agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
[PATCH 2/2] review
190+ messages / 5 participants
[nested] [flat]

* [PATCH 2/2] review
@ 2018-10-28 15:09  Tomas Vondra <tomas@2ndquadrant.com>
  0 siblings, 0 replies; 190+ messages in thread

From: Tomas Vondra @ 2018-10-28 15:09 UTC (permalink / raw)

---
 doc/src/sgml/ref/copy.sgml     |  2 +-
 src/backend/commands/copy.c    | 22 +++++++++++-----------
 src/backend/parser/gram.y      |  2 +-
 src/include/nodes/parsenodes.h |  2 +-
 4 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml
index 8088e779b6..17bedf95cc 100644
--- a/doc/src/sgml/ref/copy.sgml
+++ b/doc/src/sgml/ref/copy.sgml
@@ -366,7 +366,7 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
    </varlistentry>
 
    <varlistentry>
-    <term><literal>WHEN Clause</literal></term>
+    <term><literal>WHEN</literal></term>
     <listitem>
    <para>
     The optional <literal>WHEN</literal> clause has the general form
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 192a1c576b..8e87605135 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -803,7 +803,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	Relation	rel;
 	Oid			relid;
 	RawStmt    *query = NULL;
-	Node    *when_cluase= NULL;
+	Node	   *whenClause = NULL;
 
 	/*
 	 * Disallow COPY to/from file or program except to users with the
@@ -857,19 +857,19 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 											NULL, false, false);
 		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
-		if(stmt->whenClause)
+		if (stmt->whenClause)
 		{
 			/* add rte to column namespace  */
 			addRTEtoQuery(pstate, rte, false, false, true);
 
 			/* Transform the raw expression tree */
-			when_cluase = transformExpr(pstate, stmt->whenClause, EXPR_KIND_OTHER);
+			whenClause = transformExpr(pstate, stmt->whenClause, EXPR_KIND_OTHER);
 
 			/*  Make sure it yields a boolean result. */
-			when_cluase = coerce_to_boolean(pstate, when_cluase, "WHEN");
+			whenClause = coerce_to_boolean(pstate, whenClause, "WHEN");
 
-			when_cluase = (Node *) canonicalize_qual((Expr *) when_cluase, false);
-			when_cluase = (Node *) make_ands_implicit((Expr *) when_cluase);
+			whenClause = (Node *) canonicalize_qual((Expr *) whenClause, false);
+			whenClause = (Node *) make_ands_implicit((Expr *) whenClause);
 		}
 
 		tupDesc = RelationGetDescr(rel);
@@ -1020,7 +1020,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
 							   NULL, stmt->attlist, stmt->options);
-		cstate->whenClause=when_cluase;
+		cstate->whenClause = whenClause;
 		*processed = CopyFrom(cstate);	/* copy from file to database */
 		EndCopyFrom(cstate);
 	}
@@ -2559,9 +2559,9 @@ CopyFrom(CopyState cstate)
 			ExecSetupChildParentMapForLeaf(proute);
 	}
 
-	if(cstate->whenClause )
+	if (cstate->whenClause)
 		cstate->qualexpr = ExecInitQual(castNode(List, cstate->whenClause),
-												&mtstate->ps);
+												 &mtstate->ps);
 
 	/*
 	 * It's more efficient to prepare a bunch of tuples for insertion, and
@@ -2717,11 +2717,11 @@ next_record:
 		slot = myslot;
 		ExecStoreHeapTuple(tuple, slot, false);
 
-		if(cstate->whenClause)
+		if (cstate->whenClause)
 		{
 			econtext->ecxt_scantuple = myslot;
 			if (!ExecQual(cstate->qualexpr, econtext))
-			goto next_record;
+				goto next_record;
 		}
 
 		/* Determine the partition to heap_insert the tuple into */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0f316846b6..6063644850 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3180,7 +3180,7 @@ copy_generic_opt_arg_list:
 		;
 
 opt_when_clause:
-			WHEN a_expr							{ $$ = $2; }
+			WHEN '(' a_expr ')'						{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NULL; }
 		;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4d3a97f9bc..d9d41b5119 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1972,7 +1972,7 @@ typedef struct CopyStmt
 	bool		is_program;		/* is 'filename' a program to popen? */
 	char	   *filename;		/* filename, or NULL for STDIN/STDOUT */
 	List	   *options;		/* List of DefElem nodes */
-	Node	   *whenClause;	/* qualifications */
+	Node	   *whenClause;		/* WHEN condition (or NULL) */
 } CopyStmt;
 
 /* ----------------------
-- 
2.13.6


--------------D20F32F15C6328E5A847507E
Content-Type: text/x-patch;
 name="0001-rebased-patch.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0001-rebased-patch.patch"



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

* [PATCH 2/2] review
@ 2018-10-28 17:38  Tomas Vondra <tomas@2ndquadrant.com>
  0 siblings, 0 replies; 190+ messages in thread

From: Tomas Vondra @ 2018-10-28 17:38 UTC (permalink / raw)

---
 doc/src/sgml/ref/select.sgml            |  6 ++--
 src/backend/executor/nodeLimit.c        | 52 +++++++++++++++++++--------------
 src/backend/optimizer/plan/createplan.c | 25 +++++++---------
 src/include/nodes/execnodes.h           |  4 +--
 src/include/nodes/parsenodes.h          |  4 +--
 src/include/nodes/plannodes.h           |  2 +-
 src/include/nodes/relation.h            |  2 +-
 7 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index c81ac04108..b649b1ca7a 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -1408,9 +1408,9 @@ FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] {
     If <replaceable class="parameter">count</replaceable> is
     omitted in a <literal>FETCH</literal> clause, it defaults to 1.
     <literal>ROW</literal>
-    <literal>WITH TIES</literal> option is Used to return two or more rows 
-    that tie for last place in the limit results set according to <literal>ORDER BY</literal> 
-    clause (<literal>ORDER BY</literal> clause must be specified in this case). 
+    <literal>WITH TIES</literal> option is used to return two or more rows
+    that tie for last place in the limit results set according to <literal>ORDER BY</literal>
+    clause (<literal>ORDER BY</literal> clause must be specified in this case).
     and <literal>ROWS</literal> as well as <literal>FIRST</literal>
     and <literal>NEXT</literal> are noise words that don't influence
     the effects of these clauses.
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
index 93f8813972..724a8e09c1 100644
--- a/src/backend/executor/nodeLimit.c
+++ b/src/backend/executor/nodeLimit.c
@@ -132,7 +132,8 @@ ExecLimit(PlanState *pstate)
 				 * the state machine state to record having done so.
 				 */
 				if (!node->noCount &&
-					node->position - node->offset >= node->count && node->limitOption == WITH_ONLY)
+					node->position - node->offset >= node->count &&
+					node->limitOption == WITH_ONLY)
 				{
 					node->lstate = LIMIT_WINDOWEND;
 
@@ -145,23 +146,24 @@ ExecLimit(PlanState *pstate)
 
 					return NULL;
 				}
-
 				else if (!node->noCount &&
-					node->position - node->offset >= node->count && node->limitOption == WITH_TIES)
+						 node->position - node->offset >= node->count &&
+						 node->limitOption == WITH_TIES)
 				{
 					/*
-					* Get next tuple from subplan, if any.
-					*/
+					 * Get next tuple from subplan, if any.
+					 */
 					slot = ExecProcNode(outerPlan);
 					if (TupIsNull(slot))
 					{
 						node->lstate = LIMIT_SUBPLANEOF;
 						return NULL;
 					}
+
 					/*
-					* Test if the new tuple and the last tuple match.
-					* If so we return the tuple.
-					*/
+					 * Test if the new tuple and the last tuple match.
+					 * If so we return the tuple.
+					 */
 					econtext->ecxt_innertuple = slot;
 					econtext->ecxt_outertuple = node->last_slot;
 					if (ExecQualAndReset(node->eqfunction, econtext))
@@ -184,22 +186,25 @@ ExecLimit(PlanState *pstate)
 						return NULL;
 					}
 
-				}else
-				/*
-				 * Get next tuple from subplan, if any.
-				 */
-				slot = ExecProcNode(outerPlan);
-				if (TupIsNull(slot))
-				{
-					node->lstate = LIMIT_SUBPLANEOF;
-					return NULL;
 				}
-				if (node->limitOption == WITH_TIES)
+				else	/* XXX So what exactly should be part of this else branch? */
 				{
-					ExecCopySlot(node->last_slot, slot);
+					/*
+					 * Get next tuple from subplan, if any.
+					 */
+					slot = ExecProcNode(outerPlan);
+					if (TupIsNull(slot))
+					{
+						node->lstate = LIMIT_SUBPLANEOF;
+						return NULL;
+					}
+					if (node->limitOption == WITH_TIES)
+					{
+						ExecCopySlot(node->last_slot, slot);
+					}
+					node->subSlot = slot;
+					node->position++;
 				}
-				node->subSlot = slot;
-				node->position++;
 			}
 			else
 			{
@@ -356,7 +361,7 @@ recompute_limits(LimitState *node)
 	 * previous time we got a different result.
 	 */
 	if(node->limitOption == WITH_ONLY)
-	ExecSetTupleBound(compute_tuples_needed(node), outerPlanState(node));
+		ExecSetTupleBound(compute_tuples_needed(node), outerPlanState(node));
 }
 
 /*
@@ -433,6 +438,9 @@ ExecInitLimit(Limit *node, EState *estate, int eflags)
 	 */
 	limitstate->ps.ps_ProjInfo = NULL;
 
+	/*
+	 * Initialize the equality evaluation, to detect ties.
+	 */
 	if (node->limitOption == WITH_TIES)
 	{
 		TupleDesc       scanDesc;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 7e540849b4..62c05c7feb 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -2435,6 +2435,9 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
 {
 	Limit	   *plan;
 	Plan	   *subplan;
+	int			numsortkeys = 0;
+	AttrNumber *sortColIdx = NULL;
+	Oid		   *sortOperators = NULL;
 
 	/* Limit doesn't project, so tlist requirements pass through */
 	subplan = create_plan_recurse(root, best_path->subpath, flags);
@@ -2442,9 +2445,6 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
 	if (best_path->limitOption == WITH_TIES)
 	{
 		Query	   *parse = root->parse;
-		int			numsortkeys;
-		AttrNumber *sortColIdx;
-		Oid		   *sortOperators;
 		ListCell   *l;
 
 		numsortkeys = list_length(parse->sortClause);
@@ -2461,19 +2461,14 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
 			sortOperators[numsortkeys] = sortcl->eqop;
 			numsortkeys++;
 		}
-		plan = make_limit(subplan,
-						  best_path->limitOffset,
-						  best_path->limitCount,
-						  best_path->limitOption,
-						  numsortkeys, sortColIdx,
-						  sortOperators);
 	}
-	else
-		plan = make_limit(subplan,
-						  best_path->limitOffset,
-						  best_path->limitCount,
-						  best_path->limitOption,
-						  0, NULL, NULL);
+
+	plan = make_limit(subplan,
+					  best_path->limitOffset,
+					  best_path->limitCount,
+					  best_path->limitOption,
+					  numsortkeys, sortColIdx,
+					  sortOperators);
 
 	copy_generic_path_info(&plan->plan, (Path *) best_path);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 212820d886..6f15ce8ea4 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2242,7 +2242,7 @@ typedef struct LimitState
 	PlanState	ps;				/* its first field is NodeTag */
 	ExprState  *limitOffset;	/* OFFSET parameter, or NULL if none */
 	ExprState  *limitCount;		/* COUNT parameter, or NULL if none */
-	LimitOption  limitOption;		/* limit specification type */
+	LimitOption	limitOption;	/* limit specification type */
 	int64		offset;			/* current OFFSET value */
 	int64		count;			/* current COUNT, if any */
 	bool		noCount;		/* if true, ignore count */
@@ -2250,7 +2250,7 @@ typedef struct LimitState
 	int64		position;		/* 1-based index of last tuple returned */
 	TupleTableSlot *subSlot;	/* tuple last obtained from subplan */
 	ExprState  *eqfunction;		/* tuple equality qual in case of WITH TIES OPTION */
-	TupleTableSlot *last_slot;
+	TupleTableSlot *last_slot;	/* slot for evaluation of ties */
 } LimitState;
 
 #endif							/* EXECNODES_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ce10b55dcf..ce07a1a02d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -159,7 +159,7 @@ typedef struct Query
 
 	Node	   *limitOffset;	/* # of result tuples to skip (int8 expr) */
 	Node	   *limitCount;		/* # of result tuples to return (int8 expr) */
-	LimitOption	   limitOption;		/* limit type */
+	LimitOption	limitOption;	/* LIMIT type [WITH TIES | WITH ONLY] */
 
 	List	   *rowMarks;		/* a list of RowMarkClause's */
 
@@ -1574,7 +1574,7 @@ typedef struct SelectStmt
 	List	   *sortClause;		/* sort clause (a list of SortBy's) */
 	Node	   *limitOffset;	/* # of result tuples to skip */
 	Node	   *limitCount;		/* # of result tuples to return */
-	LimitOption	   limitOption;		/* limit type */
+	LimitOption	limitOption;	/* limit type */
 	List	   *lockingClause;	/* FOR UPDATE (list of LockingClause's) */
 	WithClause *withClause;		/* WITH clause */
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 24b7e51a91..5e961a8f7b 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -945,7 +945,7 @@ typedef struct Limit
 	Plan		plan;
 	Node	   *limitOffset;	/* OFFSET parameter, or NULL if none */
 	Node	   *limitCount;		/* COUNT parameter, or NULL if none */
-	LimitOption	   limitOption;		/* LIMIT with ties or  exact number */
+	LimitOption	limitOption;	/* LIMIT with ties or exact number */
 	int			numCols;		/* number of columns to check for Similarity  */
 	AttrNumber *uniqColIdx;		/* their indexes in the target list */
 	Oid		   *uniqOperators;	/* equality operators to compare with */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 2c7f4c71ea..f107d4f773 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1737,7 +1737,7 @@ typedef struct LimitPath
 	Path	   *subpath;		/* path representing input source */
 	Node	   *limitOffset;	/* OFFSET parameter, or NULL if none */
 	Node	   *limitCount;		/* COUNT parameter, or NULL if none */
-	LimitOption	   limitOption;		/* LIMIT with ties or  exact number */
+	LimitOption	limitOption;	/* LIMIT with ties or  exact number */
 } LimitPath;
 
 
-- 
2.13.6


--------------08FEC5268EA8E45E5EE90A4F
Content-Type: text/x-patch;
 name="0001-rebased-patch.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0001-rebased-patch.patch"



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

* [PATCH 1/2] review
@ 2021-01-09 02:17  Tomas Vondra <tomas@2ndquadrant.com>
  0 siblings, 0 replies; 190+ messages in thread

From: Tomas Vondra @ 2021-01-09 02:17 UTC (permalink / raw)

---
 doc/src/sgml/mvcc.sgml                 | 20 +++++++++++---------
 doc/src/sgml/ref/create_policy.sgml    |  6 +++---
 doc/src/sgml/ref/merge.sgml            | 12 +++++++++---
 src/backend/commands/explain.c         |  2 +-
 src/backend/executor/execMerge.c       |  5 ++++-
 src/backend/executor/nodeModifyTable.c |  3 ++-
 src/backend/optimizer/prep/preptlist.c |  6 ++++++
 src/backend/parser/parse_agg.c         |  1 +
 src/backend/replication/walsender.c    |  4 ++--
 src/backend/rewrite/rewriteHandler.c   |  4 ++++
 src/backend/utils/adt/varlena.c        |  3 +++
 src/include/nodes/parsenodes.h         |  7 ++++---
 12 files changed, 50 insertions(+), 23 deletions(-)

diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index 9ec8474185..02c9f3fdea 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -429,22 +429,24 @@ COMMIT;
     with both <command>INSERT</command> and <command>UPDATE</command>
     subcommands looks similar to <command>INSERT</command> with an
     <literal>ON CONFLICT DO UPDATE</literal> clause but does not guarantee
-    that either <command>INSERT</command> and <command>UPDATE</command> will occur.
+    that either <command>INSERT</command> or <command>UPDATE</command> will occur.
 
-    If MERGE attempts an UPDATE or DELETE and the row is concurrently updated
+	/* XXX This is a very long and hard to understand sentence :-( */
+	/* XXX Do we want to mention EvalPlanQual here? There's no explanation what it does in this file, so maybe elaborate what it does or leave that detail for a README? */
+    If MERGE attempts an <command>UPDATE</command> or <command>DELETE</command> and the row is concurrently updated
     but the join condition still passes for the current target and the current
-    source tuple, then MERGE will behave the same as the UPDATE or DELETE commands
+    source tuple, then <command>MERGE</command> will behave the same as the <command>UPDATE</command> or <command>DELETE</command> commands
     and perform its action on the latest version of the row, using standard
-    EvalPlanQual. MERGE actions can be conditional, so conditions must be
+    EvalPlanQual. <command>MERGE</command> actions can be conditional, so conditions must be
     re-evaluated on the latest row, starting from the first action.
 
     On the other hand, if the row is concurrently updated or deleted so that
-    the join condition fails, then MERGE will execute a NOT MATCHED action, if it
-    exists and the AND WHEN qual evaluates to true.
+    the join condition fails, then <command>MERGE</command> will execute a <literal>NOT MATCHED</literal> action, if it
+    exists and the <literal>AND WHEN</literal> qual evaluates to true.
 
-    If MERGE attempts an INSERT and a unique index is present and a duplicate
-    row is concurrently inserted then a uniqueness violation is raised. MERGE
-    does not attempt to avoid the ERROR by attempting an UPDATE.
+    If <command>MERGE</command> attempts an <command>INSERT</command> and a unique index is present and a duplicate
+    row is concurrently inserted then a uniqueness violation is raised. <command>MERGE</command>
+    does not attempt to avoid the <literal>ERROR</literal> by attempting an <command>UPDATE</command>.
    </para>
 
    <para>
diff --git a/doc/src/sgml/ref/create_policy.sgml b/doc/src/sgml/ref/create_policy.sgml
index ad20230c58..0a64674f2d 100644
--- a/doc/src/sgml/ref/create_policy.sgml
+++ b/doc/src/sgml/ref/create_policy.sgml
@@ -97,9 +97,9 @@ CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable
 
   <para>
    No separate policy exists for <command>MERGE</command>. Instead policies
-   defined for <literal>SELECT</literal>, <literal>INSERT</literal>,
-   <literal>UPDATE</literal> and <literal>DELETE</literal> are applied
-   while executing MERGE, depending on the actions that are activated.
+   defined for <command>SELECT</command>, <command>INSERT</command>,
+   <command>UPDATE</command> and <command>DELETE</command> are applied
+   while executing <command>MERGE</command>, depending on the actions that are activated.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/merge.sgml b/doc/src/sgml/ref/merge.sgml
index b2a9f67cfa..c2901b0d58 100644
--- a/doc/src/sgml/ref/merge.sgml
+++ b/doc/src/sgml/ref/merge.sgml
@@ -98,7 +98,7 @@ DELETE
   </para>
 
   <para>
-   There is no MERGE privilege.  
+   There is no <literal>MERGE</literal> privilege.
    You must have the <literal>UPDATE</literal> privilege on the column(s)
    of the <replaceable class="parameter">target_table_name</replaceable>
    referred to in the <literal>SET</literal> clause
@@ -217,6 +217,7 @@ DELETE
       At least one <literal>WHEN</literal> clause is required.
      </para>
      <para>
+      /* XXX Do we need to repeat the same thing for WHEN MATCHED and WHEN NOT MATCHED? Could this say that it applies to both? */
       If the <literal>WHEN</literal> clause specifies <literal>WHEN MATCHED</literal>
       and the candidate change row matches a row in the
       <replaceable class="parameter">target_table_name</replaceable>
@@ -242,6 +243,7 @@ DELETE
       clause will be activated and the corresponding action will occur for
       that row. The expression may not contain functions that possibly performs
       writes to the database.
+      /* XXX Does it mean that it has to be marked as STABLE, or that a write crashes the DB? */
      </para>
      <para>
       A condition on a <literal>WHEN MATCHED</literal> clause can refer to columns
@@ -379,6 +381,7 @@ DELETE
      <para>
       An expression to assign to the column.  The expression can use the
       old values of this and other columns in the table.
+      /* XXX Which table? Source or target, or both? What if it's NOT MATCHED? */
      </para>
     </listitem>
    </varlistentry>
@@ -492,6 +495,7 @@ MERGE <replaceable class="parameter">total-count</replaceable>
    In summary, statement triggers for an event type (say, INSERT) will
    be fired whenever we <emphasis>specify</emphasis> an action of that kind. Row-level
    triggers will fire only for the one event type <emphasis>activated</emphasis>.
+   /* XXX What does "activated" mean? Maybe "executed" would be better? */
    So a <command>MERGE</command> might fire statement triggers for both
    <command>UPDATE</command> and <command>INSERT</command>, even though only
    <command>UPDATE</command> row triggers were fired.
@@ -504,6 +508,8 @@ MERGE <replaceable class="parameter">total-count</replaceable>
    rows will be used to modify the target row, later attempts to modify will
    cause an error.  This can also occur if row triggers make changes to the
    target table which are then subsequently modified by <command>MERGE</command>.
+   /* XXX Not sure the preceding sentence makes sense. What does the 'which' refer to? Row triggers, changes, or what? */
+   /* XXX It seems the rule is that INSERT actions are <literal> while INSERT statements (in SQL sense) are <command>. But this mixes that up? */
    If the repeated action is an <command>INSERT</command> this will
    cause a uniqueness violation while a repeated <command>UPDATE</command> or
    <command>DELETE</command> will cause a cardinality violation; the latter behavior
@@ -554,7 +560,7 @@ MERGE <replaceable class="parameter">total-count</replaceable>
   <title>Examples</title>
 
   <para>
-   Perform maintenance on CustomerAccounts based upon new Transactions.
+   Perform maintenance on <literal>CustomerAccounts</literal> based upon new <literal>Transactions</literal>.
 
 <programlisting>
 MERGE CustomerAccount CA
@@ -599,7 +605,7 @@ WHEN MATCHED THEN
   DELETE;
 </programlisting>
 
-   The wine_stock_changes table might be, for example, a temporary table
+   The <literal>wine_stock_changes</literal> table might be, for example, a temporary table
    recently loaded into the database.
   </para>
 
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index fbaf50c258..f914a00e9f 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -3690,7 +3690,7 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
 			break;
 		case CMD_MERGE:
 			operation = "Merge";
-			foperation = "Foreign Merge";
+			foperation = "Foreign Merge";	/* XXX Doesn't the commit message say foreign tables are not yet supported? */
 			break;
 		default:
 			operation = "???";
diff --git a/src/backend/executor/execMerge.c b/src/backend/executor/execMerge.c
index 0e245e1361..a7492a6c4b 100644
--- a/src/backend/executor/execMerge.c
+++ b/src/backend/executor/execMerge.c
@@ -105,7 +105,7 @@ ExecMerge(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo,
 	 * A concurrent update can:
 	 *
 	 * 1. modify the target tuple so that it no longer satisfies the
-	 * additional quals attached to the current WHEN MATCHED action OR
+	 * additional quals attached to the current WHEN MATCHED action
 	 *
 	 * In this case, we are still dealing with a WHEN MATCHED case, but we
 	 * should recheck the list of WHEN MATCHED actions and choose the first
@@ -118,6 +118,9 @@ ExecMerge(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo,
 	 * tuple, so we now instead find a qualifying WHEN NOT MATCHED action to
 	 * execute.
 	 *
+	 * XXX Hmmm, what if the updated tuple would now match one that was
+	 * considered NOT MATCHED so far?
+	 *
 	 * A concurrent delete, changes a WHEN MATCHED case to WHEN NOT MATCHED.
 	 *
 	 * ExecMergeMatched takes care of following the update chain and
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c0a97eba91..9c14709e47 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2124,7 +2124,6 @@ ExecModifyTable(PlanState *pstate)
 		{
 			/* advance to next subplan if any */
 			node->mt_whichplan++;
-
 			if (node->mt_whichplan < node->mt_nplans)
 			{
 				resultRelInfo++;
@@ -2169,6 +2168,8 @@ ExecModifyTable(PlanState *pstate)
 		EvalPlanQualSetSlot(&node->mt_epqstate, planSlot);
 		slot = planSlot;
 
+		/* XXX Wouldn't it be "nicer" to handle MERGE in the switch, together
+		 * with the other MT operations? This seems a bit out of place. */
 		if (operation == CMD_MERGE)
 		{
 			ExecMerge(node, resultRelInfo, estate, slot, junkfilter);
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 8848e9a03f..b2543f6814 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -117,6 +117,10 @@ preprocess_targetlist(PlannerInfo *root)
 		tlist = expand_targetlist(tlist, command_type,
 								  result_relation, target_relation);
 
+	/*
+	 * For MERGE we need to handle the target list for the target relation,
+	 * and also target list for each action (only INSERT/UPDATE matter).
+	 */
 	if (command_type == CMD_MERGE)
 	{
 		ListCell   *l;
@@ -343,6 +347,8 @@ expand_targetlist(List *tlist, int command_type,
 			 * generate a NULL for dropped columns (we want to drop any old
 			 * values).
 			 *
+			 * XXX Should this explain why MERGE has the same logic as UPDATE?
+			 *
 			 * When generating a NULL constant for a dropped column, we label
 			 * it INT4 (any other guaranteed-to-exist datatype would do as
 			 * well). We can't label it with the dropped column's datatype
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index f7c152beb4..5d49c8c37e 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -436,6 +436,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			break;
 		case EXPR_KIND_MERGE_WHEN_AND:
 			if (isAgg)
+				/* XXX "WHEN AND" seems rather strange. ... */
 				err = _("aggregate functions are not allowed in WHEN AND conditions");
 			else
 				err = _("grouping operations are not allowed in WHEN AND conditions");
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index d59f4fde95..d50543b1ba 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1198,6 +1198,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 
 	/* Also update the sent position status in shared memory */
 	SpinLockAcquire(&MyWalSnd->mutex);
+	/* XXX Huh? Why does MERGE patch change walsender? */
 	MyWalSnd->sentPtr = MyReplicationSlot->data.confirmed_flush;
 	SpinLockRelease(&MyWalSnd->mutex);
 
@@ -2930,8 +2931,7 @@ WalSndDone(WalSndSendDataCallback send_data)
 	replicatedPtr = XLogRecPtrIsInvalid(MyWalSnd->flush) ?
 		MyWalSnd->write : MyWalSnd->flush;
 
-	if (WalSndCaughtUp &&
-		sentPtr == replicatedPtr &&
+	if (WalSndCaughtUp && sentPtr == replicatedPtr &&
 		!pq_is_send_pending())
 	{
 		QueryCompletion qc;
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index e2706c181c..80ae946d17 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1774,6 +1774,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation)
 	}
 }
 
+
 /*
  * matchLocks -
  *	  match the list of locks and returns the matching rules
@@ -3946,6 +3947,9 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		/*
 		 * XXX MERGE doesn't support write rules because they would violate
 		 * the SQL Standard spec and would be unclear how they should work.
+		 *
+		 * XXX So does't support means 'ignores'? Should that either fail
+		 * or at least print some warning?
 		 */
 		if (event == CMD_MERGE)
 			product_queries = NIL;
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 0281351dcf..7a768a7b5b 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2312,6 +2312,7 @@ varstrfastcmp_locale(char *a1p, int len1, char *a2p, int len2, SortSupport ssup)
 	int			result;
 	bool		arg1_match;
 
+	/* XXX Huh? Why is this in MERGE patch? */
 	if (len1 < 0 || len2 < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATA_CORRUPTED),
@@ -2505,6 +2506,7 @@ varstr_abbrev_convert(Datum original, SortSupport ssup)
 	memset(pres, 0, sizeof(Datum));
 	len = VARSIZE_ANY_EXHDR(authoritative);
 
+	/* XXX Huh? Why is this in MERGE patch? */
 	if (len < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATA_CORRUPTED),
@@ -3260,6 +3262,7 @@ bytea_catenate(bytea *t1, bytea *t2)
 	len1 = VARSIZE_ANY_EXHDR(t1);
 	len2 = VARSIZE_ANY_EXHDR(t2);
 
+	/* XXX Huh? Why is this in MERGE patch? */
 	if (len1 < 0 || len2 < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATA_CORRUPTED),
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3cba38044e..08fea9b8f7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -172,6 +172,7 @@ typedef struct Query
 	List	   *withCheckOptions;	/* a list of WithCheckOption's (added
 									 * during rewrite) */
 
+	/* XXX Why not mergeTragetRelation? */
 	int			mergeTarget_relation;
 	List	   *mergeSourceTargetList;
 	List	   *mergeActionList;	/* list of actions for MERGE (only) */
@@ -1581,11 +1582,11 @@ typedef struct UpdateStmt
 typedef struct MergeStmt
 {
 	NodeTag		type;
-	RangeVar   *relation;		/* target relation to merge into */
+	RangeVar   *relation;			/* target relation to merge into */
 	Node	   *source_relation;	/* source relation */
-	Node	   *join_condition; /* join condition between source and target */
+	Node	   *join_condition; 	/* join condition between source and target */
 	List	   *mergeWhenClauses;	/* list of MergeWhenClause(es) */
-	WithClause *withClause;		/* WITH clause */
+	WithClause *withClause;			/* WITH clause */
 } MergeStmt;
 
 typedef struct MergeWhenClause
-- 
2.26.2


--------------B1AA4DC5EAE9BAF8B146244A
Content-Type: text/plain; charset=UTF-8;
 name="0002-fix-valgrind-failure.txt"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0002-fix-valgrind-failure.txt"



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

* [PATCH 2/2] review
@ 2021-03-09 20:09  Tomas Vondra <tomas.vondra@postgresql.org>
  0 siblings, 0 replies; 190+ messages in thread

From: Tomas Vondra @ 2021-03-09 20:09 UTC (permalink / raw)

---
 src/backend/optimizer/path/costsize.c | 77 ++++++++++++++++++++-------
 src/backend/optimizer/path/pathkeys.c | 14 ++++-
 2 files changed, 69 insertions(+), 22 deletions(-)

diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1639258aaf..f55a4f20e5 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -1755,6 +1755,8 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm)
  * is_fake_var
  *		Workaround for generate_append_tlist() which generates fake Vars with
  *		varno == 0, that will cause a fail of estimate_num_group() call
+ *
+ * XXX Ummm, why would estimate_num_group fail with this?
  */
 static bool
 is_fake_var(Expr *expr)
@@ -1828,27 +1830,53 @@ get_width_cost_multiplier(PlannerInfo *root, Expr *expr)
  * compute_cpu_sort_cost
  *		compute CPU cost of sort (i.e. in-memory)
  *
+ * The main thing we need to calculate to estimate sort CPU costs is the number
+ * of calls to the comparator functions. The difficulty is that for multi-column
+ * sorts there may be different data types involved (for some of which the calls
+ * may be much more expensive). Furthermore, the columns may have very different
+ * number of distinct values - the higher the number, the fewer comparisons will
+ * be needed for the following columns.
+ *
+ * The algoritm is incremental - we add pathkeys one by one, and at each step we
+ * estimate the number of necessary comparisons (based on the number of distinct
+ * groups in the current pathkey prefix and the new pathkey), and the comparison
+ * costs (which is data type specific).
+ *
+ * Estimation of the number of comparisons is based on ideas from two sources:
+ *
+ * 1) "Algorithms" (course), Robert Sedgewick, Kevin Wayne [https://algs4.cs.princeton.edu/home/]
+ *
+ * 2) "Quicksort Is Optimal For Many Equal Keys" (paper), Sebastian Wild,
+ * arXiv:1608.04906v4 [cs.DS] 1 Nov 2017. [https://arxiv.org/abs/1608.04906]
+ *
+ * In term of that paper, let N - number of tuples, Xi - number of tuples with
+ * key Ki, then the estimate of number of comparisons is:
+ *
+ *	log(N! / (X1! * X2! * ..))  ~  sum(Xi * log(N/Xi))
+ *
+ * In our case all Xi are the same because now we don't have any estimation of
+ * group sizes, we have only know the estimate of number of groups (distinct
+ * values). In that case, formula becomes:
+ *
+ *	N * log(NumberOfGroups)
+ *
+ * For multi-column sorts we need to estimate the number of comparisons for
+ * each individual column - for example with columns (c1, c2, ..., ck) we
+ * can estimate that number of comparions on ck is roughly
+ *
+ *	ncomparisons(c1, c2, ..., ck) / ncomparisons(c1, c2, ..., c(k-1))
+ *
+ * Let k be a column number, Gk - number of groups defined by k columns, and Fk
+ * the cost of the comparison is
+ *
+ *	N * sum( Fk * log(Gk) )
+ *
+ * Note: We also consider column witdth, not just the comparator cost.
+ *
  * NOTE: some callers currently pass NIL for pathkeys because they
  * can't conveniently supply the sort keys.  In this case, it will fallback to
  * simple comparison cost estimate.
- *
- * Estimation algorithm is based on ideas from course Algorithms,
- * Robert Sedgewick, Kevin Wayne, https://algs4.cs.princeton.edu/home/ and paper
- * "Quicksort Is Optimal For Many Equal Keys", Sebastian Wild,
- * arXiv:1608.04906v4 [cs.DS] 1 Nov 2017.
- *
- * In term of that papers, let N - number of tuples, Xi - number of tuples with
- * key Ki, then estimation is:
- * log(N! / (X1! * X2! * ..))  ~  sum(Xi * log(N/Xi))
- * In our case all Xi are the same because now we don't have an estimation of
- * group sizes, we have only estimation of number of groups. In this case,
- * formula becomes: N * log(NumberOfGroups). Next, to support correct estimation
- * of multi-column sort we need separately compute each column, so, let k is a
- * column number, Gk - number of groups  defined by k columns:
- * N * sum( Fk * log(Gk) )
- * Fk is a function costs (including width) for k columns.
  */
-
 static Cost
 compute_cpu_sort_cost(PlannerInfo *root, List *pathkeys, int nPresortedKeys,
 					  Cost comparison_cost, double tuples, double output_tuples,
@@ -1862,7 +1890,7 @@ compute_cpu_sort_cost(PlannerInfo *root, List *pathkeys, int nPresortedKeys,
 	bool		has_fake_var = false;
 	int			i = 0;
 	Oid			prev_datatype = InvalidOid;
-	Cost		funcCost = 0.;
+	Cost		funcCost = 0.0;
 	List		*cache_varinfos = NIL;
 
 	/* fallback if pathkeys is unknown */
@@ -1873,6 +1901,10 @@ compute_cpu_sort_cost(PlannerInfo *root, List *pathkeys, int nPresortedKeys,
 		 * a total number of tuple comparisons of N log2 K; but the constant
 		 * factor is a bit higher than for quicksort.  Tweak it so that the
 		 * cost curve is continuous at the crossover point.
+		 *
+		 * XXX I suppose the "quicksort factor" references to 1.5 at the end
+		 * of this function, but I'm not sure. I suggest we introduce some simple
+		 * constants for that, instead of magic values.
 		 */
 		output_tuples = (heapSort) ? 2.0 * output_tuples : tuples;
 		per_tuple_cost += 2.0 * cpu_operator_cost * LOG2(output_tuples);
@@ -1888,7 +1920,6 @@ compute_cpu_sort_cost(PlannerInfo *root, List *pathkeys, int nPresortedKeys,
 	 * - per column comparison function cost
 	 * - we try to compute needed number of comparison per column
 	 */
-
 	foreach(lc, pathkeys)
 	{
 		PathKey				*pathkey = (PathKey*)lfirst(lc);
@@ -1952,6 +1983,11 @@ compute_cpu_sort_cost(PlannerInfo *root, List *pathkeys, int nPresortedKeys,
 			 * Don't use DEFAULT_NUM_DISTINCT because it used for only one
 			 * column while here we try to estimate number of groups over
 			 * set of columns.
+			 *
+			 * XXX Perhaps this should use DEFAULT_NUM_DISTINCT at least to
+			 * limit the calculated values, somehow?
+			 *
+			 * XXX What's the logic of the following formula?
 			 */
 			nGroups = ceil(2.0 + sqrt(tuples) *
 				list_length(pathkeyExprs) / list_length(pathkeys));
@@ -1968,6 +2004,7 @@ compute_cpu_sort_cost(PlannerInfo *root, List *pathkeys, int nPresortedKeys,
 			{
 				if (tuplesPerPrevGroup < output_tuples)
 					/* comparing only inside output_tuples */
+					/* XXX why not to use the same multiplier (1.5)? */
 					correctedNGroups =
 						ceil(2.0 * output_tuples / (tuplesPerPrevGroup / nGroups));
 				else
@@ -1993,7 +2030,7 @@ compute_cpu_sort_cost(PlannerInfo *root, List *pathkeys, int nPresortedKeys,
 		tuplesPerPrevGroup = ceil(1.5 * tuplesPerPrevGroup / nGroups);
 
 		/*
-		 * We could skip all followed columns for cost estimation, because we
+		 * We could skip all following columns for cost estimation, because we
 		 * believe that tuples are unique by set ot previous columns
 		 */
 		if (tuplesPerPrevGroup <= 1.0)
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 7beb32488a..b092c3e055 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -515,10 +515,19 @@ pathkey_sort_cost_comparator(const void *_a, const void *_b)
 		return 0;
 	return 1;
 }
+
 /*
  * Order tail of list of group pathkeys by uniqueness descendetly. It allows to
  * speedup sorting. Returns newly allocated lists, old ones stay untouched.
  * n_preordered defines a head of list which order should be prevented.
+ *
+ * XXX But we're not generating this only based on uniqueness (that's a bad
+ * term anyway, because we're using ndistinct estimates, not uniqueness).
+ * We're also using the comparator cost to calculate the expected sort cost,
+ * and optimize that.
+ *
+ * XXX This should explain how we generate the values - all permutations for
+ * up to 4 values, etc.
  */
 void
 get_cheapest_group_keys_order(PlannerInfo *root, double nrows,
@@ -597,8 +606,9 @@ get_cheapest_group_keys_order(PlannerInfo *root, double nrows,
 	else
 	{
 		/*
-		 * Since v13 list_free() can clean list elements so for original list not to be modified it should be copied to
-		 * a new one which can then be cleaned safely if needed.
+		 * Since v13 list_free() can clean list elements so for original list
+		 * not to be modified it should be copied to a new one which can then
+		 * be cleaned safely if needed.
 		 */
 		new_group_pathkeys = list_copy(*group_pathkeys);
 		nToPermute = nFreeKeys;
-- 
2.29.2


--------------A955BFDEDE369DCD57A45C5A--





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

* [PATCH 02/10] review
@ 2021-03-16 16:29  Tomas Vondra <tomas.vondra@postgresql.org>
  0 siblings, 0 replies; 190+ messages in thread

From: Tomas Vondra @ 2021-03-16 16:29 UTC (permalink / raw)

---
 src/backend/optimizer/path/allpaths.c  | 2 ++
 src/backend/optimizer/plan/initsplan.c | 9 ++++++++-
 src/backend/optimizer/util/plancat.c   | 3 +++
 3 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..fc1a3a68a2 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -1065,8 +1065,10 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 				/* Whole row is not null, so must be same for child */
 				childrel->notnullattrs = bms_add_member(childrel->notnullattrs,
 														attno - FirstLowInvalidHeapAttributeNumber);
+				/* XXX shouldn't this be a continue, instead of a break? */
 				break;
 			}
+			/* XXX isn't this missing 'else'? */
 			if (attno < 0 )
 				/* no need to translate system column */
 				child_attno = attno;
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index d27167dc76..4ef876cf7b 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -829,7 +829,14 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 		{
 			Node	   *qual = (Node *) lfirst(l);
 
-			/* Set the not null info now */
+			/* Set the not null info now
+			 *
+			 * XXX Why now? Why is this the right place to do this? Does it need
+			 * to happen before distribute_qual_to_rels, for example?
+			 *
+			 * XXX Not clear to me why this looks at non-nullable vars? Shouldn't
+			 * we already have the bitmap built from atnums (from get_relation_info)?
+			 */
 			ListCell	*lc;
 			List		*non_nullable_vars = find_nonnullable_vars(qual);
 			foreach(lc, non_nullable_vars)
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index eebabcfccf..cd703e41ba 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -481,6 +481,9 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		set_relation_partition_info(root, rel, relation);
 
+	/*
+	 * Build information about which attributes are marked as NOT NULL.
+	 */
 	Assert(rel->notnullattrs == NULL);
 	for(i = 0; i < relation->rd_att->natts; i++)
 	{
-- 
2.30.2


--------------F495F6B18672582269F00FF9
Content-Type: text/x-patch; charset=UTF-8;
 name="0003-Introduce-UniqueKey-attributes-on-RelOptInf-20210317.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-Introduce-UniqueKey-attributes-on-RelOptInf-20210317.pa";
 filename*1="tch"



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

* [PATCH 04/10] review
@ 2021-03-16 18:26  Tomas Vondra <tomas.vondra@postgresql.org>
  0 siblings, 0 replies; 190+ messages in thread

From: Tomas Vondra @ 2021-03-16 18:26 UTC (permalink / raw)

---
 src/backend/nodes/copyfuncs.c               |   4 +
 src/backend/nodes/makefuncs.c               |   1 -
 src/backend/nodes/outfuncs.c                |   1 +
 src/backend/nodes/readfuncs.c               |   2 +
 src/backend/optimizer/path/README.uniquekey | 285 +++---
 src/backend/optimizer/path/allpaths.c       |  10 +-
 src/backend/optimizer/path/joinrels.c       |   7 +
 src/backend/optimizer/path/uniquekeys.c     | 906 ++++++++++++++------
 src/backend/optimizer/plan/planner.c        |  10 +
 src/backend/optimizer/prep/prepunion.c      |   1 +
 src/backend/optimizer/util/inherit.c        |   1 +
 11 files changed, 863 insertions(+), 365 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 75c1c5e824..9d832ddc03 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,9 @@ _copyPathKey(const PathKey *from)
 	return newnode;
 }
 
+/*
+ * _copyUniqueKey
+ */
 static UniqueKey *
 _copyUniqueKey(const UniqueKey *from)
 {
@@ -2306,6 +2309,7 @@ _copyUniqueKey(const UniqueKey *from)
 
 	return newnode;
 }
+
 /*
  * _copyRestrictInfo
  */
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 40415d0f5b..e156c9cdf8 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -816,7 +816,6 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	return v;
 }
 
-
 /*
  * makeUniqueKey
  */
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 44154cde6a..13905e6037 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2460,6 +2460,7 @@ static void
 _outUniqueKey(StringInfo str, const UniqueKey *node)
 {
 	WRITE_NODE_TYPE("UNIQUEKEY");
+
 	WRITE_NODE_FIELD(exprs);
 	WRITE_BOOL_FIELD(multi_nullvals);
 }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b3e212bf1c..8830c8df99 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -496,8 +496,10 @@ static UniqueKey *
 _readUniqueKey(void)
 {
 	READ_LOCALS(UniqueKey);
+
 	READ_NODE_FIELD(exprs);
 	READ_BOOL_FIELD(multi_nullvals);
+
 	READ_DONE();
 }
 
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
index 5eac761995..31cdb5ed65 100644
--- a/src/backend/optimizer/path/README.uniquekey
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -1,131 +1,208 @@
-1. What is UniqueKey?
-We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
-that doesn't yields same result among all the rows. The simplest UniqueKey
-format is primary key.
+review comments:
+XXX Maybe move this to src/backend/optimizer/README.uniquekey?
+XXX multi_nullvals name seems a bit weird
+XXX no info about  populate_distinctrel_uniquekeys, populate_grouprel_uniquekeys, populate_unionrel_uniquekeys
+-----
+src/backend/optimizer/path/README.uniquekey
+
+UniqueKey
+=========
+
+UniqueKey is a set of exprs for a RelOptInfo, which are known to have unique
+values on all the rows in the relation.  A trivial example is a primary key
+defined on a relation - each attributes of the constraint is a unique key.
+
+We can use this knowledge to perform optimization in a number of places. Some
+of the optimizations are fairly obvious, others are less so:
+
+1. remove DISTINCT node if the clause is unique
+2. remove aggregation if group by clause is unique
+3. remove_useless_joins
+4. reduce_semianti_joins
+5. Index Skip Scan (WIP)
+6. Aggregation Push-Down without 2-phase aggregation if the join can't
+   duplicate the aggregated rows. (WIP)
+
 
-However we define the UnqiueKey as below.
+UniqueKey struct
+----------------
 
-typedef struct UniqueKey
-{
+A UnqiueKey is represented by the following struct:
+
+    typedef struct UniqueKey
+    {
         NodeTag	type;
         List	*exprs;
         bool	multi_nullvals;
-} UniqueKey;
-
-exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
-is a special case of UniqueKey, which means there is only one row in that
-relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
-normal unique key and may have different values. SELECT colx FROM t WHERE uk =
-const.  colx is unique AND we have only 1 value. This field can used for
-innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
-function.
-
-multi_nullvals: true means multi null values may exist in these exprs, so the
-uniqueness is not guaranteed in this case. This field is necessary for
-remove_useless_join & reduce_unique_semijoins where we don't mind these
-duplicated NULL values. It is set to true for 2 cases. One is a unique key
-from a unique index but the related column is nullable. The other one is for
-outer join. see populate_joinrel_uniquekeys for detail.
-
-
-The UniqueKey can be used at the following cases at least:
-1. remove_useless_joins.
-2. reduce_semianti_joins
-3. remove distinct node if distinct clause is unique.
-4. remove aggnode if group by clause is unique.
-5. Index Skip Scan (WIP)
-6. Aggregation Push Down without 2 phase aggregation if the join can't
-   duplicated the aggregated rows. (work in progress feature)
-
-2. How is it maintained?
-
-We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
-various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
-groupedrel, unionrel. and we also need to convert the uniquekey from subquery
-to outer relation, which is what convert_subquery_uniquekeys does.
-
-1. The first part is about baserel. We handled 3 cases. suppose we have Unique
-Index on (a, b).
-
-1. SELECT a, b FROM t.  UniqueKey (a, b)
-2. SELECT a FROM t WHERE b = 1;  UniqueKey (a)
-3. SELECT .. FROM t WHERE a = 1 AND b = 1;  UniqueKey (NIL).  onerow case, every
-   column is Unique.
-
-2. The next part is joinrel, this part is most error-prone, we simplified the rules
-like below:
-1. If the relation's UniqueKey can't be duplicated after join,  then is will be
-   still valid for the join rel. The function we used here is
-   innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
-
-2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
-   UniqueKey from both sides are valid for sure.  We can prove this as: if the
-   unique exprs from rel1 is duplicated by rel2, the duplicated rows must
-   contains different unique exprs from rel2.
-
-More considerations about onerow:
-1. If relation with one row and it can't be duplicated, it is still possible
-   contains mulit_nullvas after outer join.
-2. If the either UniqueKey can be duplicated after join, the can get one row
-   only when both side is one row AND there is no outer join.
-3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
-   row UniqueKey to normal unique key since we don't store exprs for one-row
-   relation. get_exprs_from_uniquekeys will be used here.
-
-
-More considerations about multi_nullvals after join:
+    } UniqueKey;
+
+exprs is a list of exprs which are know to be unique on current RelOptInfo.
+
+exprs = NIL is a special case, meaning there is only one row in the relation.
+This has has a stronger semantic than others.  Consider for example
+
+    SELECT uk FROM t
+
+where 'uk' is a unique key. This guarantees uniqueness, but there may be mamy
+rows in the relation.  On the other hand, consider this query
+
+    SELECT colx FROM t WHERE uk = const
+
+In this case we know there's only a single matching row (thanks to a condition
+on the unique key), which in turn guarantees uniqueness of the colx value, even
+if there is no constraint on the column itself.
+
+This knowledge is used in innerrel_is_unique, and is handled as a special case
+in add_uniquekey_for_onerow.
+
+
+The multi_nullvals field tracks whether the expressions may contain multiple
+NULL values.  This can happen for example when the unique key is derived from
+a unique index with nullable columns, or because of outer joins (which may add
+NULL values to a known-unique list - see populate_joinrel_uniquekeys).
+
+In this case uniqueness is not guaranteed, but we can still use the information
+in places places where NULL values are harmless - when removing useless joins,
+reducing semijoins, and so on.
+
+
+How is it maintained?
+---------------------
+
+Deducing the unique keys depends on the type of the relation - for each case
+there's a separate "populate" function:
+
+
+populate_baserel_uniquekeys
+---------------------------
+
+There are three cases, all assuming there's a unique index (e.g. on (a,b)):
+
+1. SELECT a, b FROM t                      => UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1             => UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; => UniqueKey (NIL)
+
+The last query is the "one row" case, in which case every column is Unique.
+
+
+populate_joinrel_uniquekeys
+---------------------------
+
+For joins, deducing the unique keys may be fairly complex and error-prone.
+We've simplified the rules like this:
+
+1. If the UniqueKey on an input relation can't be duplicated by the join, then
+it will be valid for the join rel. A typical example is a join like this:
+
+    inner_rel.any_col = outer_rel.unique_key
+
+The function used to detect this is innerrel_keeps_unique.
+
+2. Any combination of unique keys on each side of the join is a unique key
+for the join relation.  This can be proved by contradiction - assume we have
+unique key on either side of the join - uk1 and uk2. If the values in uk1 get
+duplicated by the join with uk2 (by matching the row to multiple rows), the
+duplicated rows must have different values in the uk2.
+
+We can also leverage information about the "one row" case:
+
+1. If one of the input relations is known to have a single row, and the join
+can't duplicate the row (e.g. semi/anti join), we can keep the unique keys.
+It may however contain multi_nullvals after an outer join.
+
+XXX Not sure I understand the original logic/wording :-(
+
+2. If either UniqueKey can be duplicated after a join, there can be only one
+row only when both sides are "one row" AND there is no outer join.
+
+XXX Why the restriction on not allowing outer joins?
+
+3. Whenever the one row UniqueKey is not a valid any more, we need to convert
+UniqueKey to normal unique key since we don't store exprs for one-row relation.
+This is done by get_exprs_from_uniquekeys.
+
+The join case needs to be careful about multi_nullvals too:
+
 1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
-   mulit_nullvals in any case.
-2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
-   allows some outer join.
+mulit_nullvals in any case too.
+
+2. If the original unique key doesn't allow multi_nullvals, the unique key for
+the join relation may allow multi_nullvals after an outer join.
+
+
+subqueries
+----------
+
+It's necessary to "translate" unique keys between a subquery and the outer rels,
+which is what convert_subquery_uniquekeys does.  This does almost exactly what
+convert_subquery_pathkeys does for pathkeys.  It keeps only unique keys matching
+Vars in the outer relation.  The relationship between outerrel.Var and
+subquery.exprs is built from outerel->subroot->processed_tlist.
 
 
-3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
-convert_subquery_pathkeys.  Only the UniqueKey insides subquery is referenced as
-a Var in outer relation will be reused. The relationship between the outerrel.Var
-and subquery.exprs is built with outerel->subroot->processed_tlist.
+set-returning functions
+------------------------
 
+As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner.  So
+we will maintain the UniqueKey until there and reset it to NIL at that place.
 
-4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
-is handled in adjust_paths_for_srfs, which happens after the query_planner. so
-we will maintain the UniqueKey until there and reset it to NIL at that
-places. This can't help on distinct/group by elimination cases but probably help
-in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
-semantic correctly.
+This can't help on distinct/group by elimination cases but probably help in some
+other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is correct.
 
 
-5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
-partitioned table we need to maintain 2 different kinds of
-UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
-relation for partition wise query.
+populate_partitionedrel_uniquekeys
+----------------------------------
+
+As for inherit table, we first build the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain two different kinds of UniqueKey:
+
+1) UniqueKey on the parent relation
+
+2) UniqueKey on child
+
+This is needed because a unique key from the partition may not be be unique key
+on the partitioned table.
+
 
 Example:
-CREATE TABLE p (a int not null, b int not null) partition by list (a);
+
+CREATE TABLE p (a INT NOT NULL, b INT NOT NULL) PARTITION BY LIST (a);
+
 CREATE TABLE p0 partition of p for values in (1);
 CREATE TABLE p1 partition of p for values in (2);
 
-create unique index p0_b on p0(b);
-create unique index p1_b on p1(b);
+CREATE UNIQUE INDEX p0_b ON p0(b);
+CREATE UNIQUE INDEX p1_b ON p1(b);
 
-Now b is only unique on partition level, so the distinct can't be removed on
-the following cases. SELECT DISTINCT b FROM p;
+SELECT DISTINCT b FROM p;
 
-Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
-partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
-parent relation.
+Now "b" is only unique on partition level, but the two partitions may contain
+duplicate values for the "b" column (with different values in "a"). That means
+the DISTINCT clause can't be removed.
 
-Another usage of UniqueKey on partition level is it be helpful for
-partition-wise join.
+Now consider:
 
-As for the UniqueKey on parent table level, it comes with 2 different ways,
-1). the UniqueKey is also derived in UniqueKey index, but the index must be same
-in all the related children relations and the unique index must contains
-Partition Key in it. Example:
+SELECT DISTINCT a, b FROM p WHERE a = 1
+
+In this case, the optimizer eliminates all partitions except for one, so that
+the UniqueKey is valid for the parent relation too.
+
+UniqueKey at a partition level is useful for partition-wise join too.
+
+XXX Explain why is it useful?
+
+A UniqueKey from a partition can be transferred to the parent relation, in two
+cases.  A trivial case is if there's a single child relation (e.g. thanks to
+partition elimination). In that case all unique keys on the child relation are
+automatically valid for the parent relation.  If there are multiple relations,
+the unique key must be defived from an index present in all partitions, and the
+index has to include the partition key.
+
+Example:
 
 CREATE UNIQUE INDEX p_ab ON p(a, b);  -- where a is the partition key.
 
 -- Query
 SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
 
-2). If the parent relation has only one childrel, the UniqueKey on childrel is
- the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 66bf6f19f7..a801707eaa 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -581,7 +581,8 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/*
 	 * Now that we've marked which partial indexes are suitable, we can now
-	 * build the relation's unique keys.
+	 * build the relation's unique keys.  We need to do it in this order,
+	 * so that we don't deduce unique keys from inapplicable partial indexes.
 	 */
 	populate_baserel_uniquekeys(root, rel, rel->indexlist);
 
@@ -1305,6 +1306,12 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 
 	/* Add paths to the append relation. */
 	add_paths_to_append_rel(root, rel, live_childrels);
+
+	/*
+	 * XXX Maybe move the check into populate populate_partitionedrel_uniquekeys?
+	 * XXX What if it's append rel (but not partitioned one), but there's only one
+	 * child relation? We could still deduce unique keys, no?
+	 */
 	if (IS_PARTITIONED_REL(rel))
 		populate_partitionedrel_uniquekeys(root, rel, live_childrels);
 }
@@ -2314,6 +2321,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 										  pathkeys, required_outer));
 	}
 
+	/* Convert subpath's unique keys to outer representation */
 	convert_subquery_uniquekeys(root, rel, sub_final_rel);
 
 	/* If outer rel allows parallelism, do same for partial paths. */
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 7271f044ec..eefba449d6 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -925,6 +925,13 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
 	/* Apply partitionwise join technique, if possible. */
 	try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
 
+	/*
+	 * Determine which of the unique keys from input relations are applicable
+	 * for the join result.
+	 *
+	 * XXX We do this after trying the partitionwise join, because that may allow
+	 * using additional unique keys.
+	 */
 	populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
 }
 
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
index 77ed2b2eff..114e8334f5 100644
--- a/src/backend/optimizer/path/uniquekeys.c
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -36,7 +36,7 @@ typedef struct UniqueKeyContextData
 	bool	useful;
 } *UniqueKeyContext;
 
-static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static List *initialize_uniquecontext_for_joinrel(RelOptInfo *inputrel);
 static bool innerrel_keeps_unique(PlannerInfo *root,
 								  RelOptInfo *outerrel,
 								  RelOptInfo *innerrel,
@@ -80,8 +80,20 @@ static void add_uniquekey_from_sortgroups(PlannerInfo *root,
 
 /*
  * populate_baserel_uniquekeys
- *		Populate 'baserel' uniquekeys list by looking at the rel's unique index
- * and baserestrictinfo
+ *		Build list of unique keys for the base relation.
+ *
+ * Inspects unique indexes defined on the relation and determines what
+ * unique keys are valid.  Partial indexes are considered too, if the
+ * predicate is valid.
+ *
+ * This also inspects baserestrictinfo, because we need to determine
+ * which opclass families are interesting when inspecting indexes. If we
+ * have a unique index and distinct clause with a mismatching opclasses,
+ * we should not use that.
+ *
+ * XXX Why does this look at baserestrictinfo?
+ *
+ * XXX What about collations?
  */
 void
 populate_baserel_uniquekeys(PlannerInfo *root,
@@ -99,22 +111,48 @@ populate_baserel_uniquekeys(PlannerInfo *root,
 
 	Assert(baserel->rtekind == RTE_RELATION);
 
+	if (!indexlist)
+		return;
+
+	/*
+	 * Determine which unique indexes to use to build the unique keys.
+	 * We have to skip partial with predicates not matched by the query,
+	 * and unique indexes that are not immediately enforced.
+	 *
+	 * XXX Do we actually skip indexes that are not immediate?
+	 * XXX What about hypothetical indexes?
+	 */
 	foreach(lc, indexlist)
 	{
 		IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+
 		if (!ind->unique || !ind->immediate ||
 			(ind->indpred != NIL && !ind->predOK))
 			continue;
+
 		matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
 	}
 
+	/* If there are not applicable unique indexes, we're done. */
 	if (matched_uniq_indexes  == NIL)
 		return;
 
-	/* Check which attrs is used in baserel->reltarget */
-	pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+	/*
+	 * Determine which attrs are referenced in baserel->reltarget.  To use the
+	 * unique key info, we need all the columns - a unique index on (a,b) may
+	 * not be unique on (a).  If a column is missing in reltarget, the nodes
+	 * above can't possibly use it, and we can just ignore any matching index.
+	 */
+	pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, &used_attrs);
 
-	/* Check which attrno is used at a mergeable const filter */
+	/*
+	 * Check which attrno is used at a mergeable const filter
+	 *
+	 * XXX This is not lookint att attrno at all, maybe obsolete comment?
+	 *
+	 * Seems the primary purpose of this is determining which opclass
+	 * families to use when matching unique indexes in the next loop?
+	 */
 	foreach(lc, baserel->baserestrictinfo)
 	{
 		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
@@ -122,6 +160,10 @@ populate_baserel_uniquekeys(PlannerInfo *root,
 		if (rinfo->mergeopfamilies == NIL)
 			continue;
 
+		/*
+		 * XXX What if bms_is_empty is true for both left_relids/right_relids?
+		 * Or what if it's false in both cases?
+		 */
 		if (bms_is_empty(rinfo->left_relids))
 		{
 			const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
@@ -136,40 +178,69 @@ populate_baserel_uniquekeys(PlannerInfo *root,
 		expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
 	}
 
+	/*
+	 * Now try to match unique indexes to attributes in reltarget, and to
+	 * merge operator families. The index may be on the right attributes,
+	 * but if it's not matching the opfamily it's useless.
+	 *
+	 * XXX Can we have multiple baserestrictinfo for the same attribute,
+	 * with different opfamilies? Probably not.
+	 */
 	foreach(lc, matched_uniq_indexes)
 	{
-		bool	multi_nullvals, useful;
-		List	*exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
-													const_exprs,
-													expr_opfamilies,
-													used_attrs,
-													&useful,
-													&multi_nullvals);
-		if (useful)
+		bool	multi_nullvals,
+				useful;
+
+		IndexOptInfo *index_info = (IndexOptInfo *) lfirst_node(IndexOptInfo, lc);
+
+		List   *exprs = get_exprs_from_uniqueindex(index_info,
+												   const_exprs,
+												   expr_opfamilies,
+												   used_attrs,
+												   &useful,
+												   &multi_nullvals);
+
+		if (!useful)
+			continue;
+
+		/*
+		 * All the columns in Unique Index matched with a restrictinfo, so
+		 * that we know there's just a one row in the result. If we find
+		 * such index, we're done - we discard all other unique keys and
+		 * keep just this special one. In principle, this is a stronger
+		 * guarantee, because all subsets of one row are still unique.
+		 *
+		 * XXX Is it correct to just return? Doesn't that prevent some
+		 * optimizations that might be possible with the other keys?
+		 */
+		if (exprs == NIL)
 		{
-			if (exprs == NIL)
-			{
-				/* All the columns in Unique Index matched with a restrictinfo */
-				add_uniquekey_for_onerow(baserel);
-				return;
-			}
-			baserel->uniquekeys = lappend(baserel->uniquekeys,
-										  makeUniqueKey(exprs, multi_nullvals));
+			/* discards all previous uniquekeys */
+			add_uniquekey_for_onerow(baserel);
+			return;
 		}
+
+		baserel->uniquekeys = lappend(baserel->uniquekeys,
+									  makeUniqueKey(exprs, multi_nullvals));
 	}
 }
 
 
 /*
  * populate_partitionedrel_uniquekeys
- * The UniqueKey on partitionrel comes from 2 cases:
- * 1). Only one partition is involved in this query, the unique key can be
- * copied to parent rel from childrel.
- * 2). There are some unique index which includes partition key and exists
- * in all the related partitions.
- * We never mind rule 2 if we hit rule 1.
+ *		Determine unique keys for a partitioned relation.
+ *
+ * Inspects unique keys for all partitions and derives unique keys that
+ * are valid for the whole partitioned table. There are two basic cases:
+ *
+ * 1) There's only one remaining partition (thanks to pruning all other
+ * partitions). In this case all the unique keys from the partition are
+ * trivially valid for the partitioned table.
+ *
+ * 2) All the partitions have the same unique index (on the same set of
+ * columns), and the index includes the partition key. This ensures the
+ * combination of values is unique for the whole partitioned table.
  */
-
 void
 populate_partitionedrel_uniquekeys(PlannerInfo *root,
 								   RelOptInfo *rel,
@@ -180,110 +251,181 @@ populate_partitionedrel_uniquekeys(PlannerInfo *root,
 	RelOptInfo *childrel;
 	bool is_first = true;
 
+	/* XXX What about append rels? At least for the one-child case? */
 	Assert(IS_PARTITIONED_REL(rel));
 
+	/* if there are no child relations, we're done. */
 	if (childrels == NIL)
 		return;
 
 	/*
-	 * If there is only one partition used in this query, the UniqueKey in childrel is
-	 * still valid in parent level, but we need convert the format from child expr to
-	 * parent expr.
+	 * If there is only one partition used in this query, the UniqueKey for
+	 * a child relation is still valid for the parent level. We need to
+	 * convert the format from child expr to parent expr.
 	 */
 	if (list_length(childrels) == 1)
 	{
-		/* Check for Rule 1 */
 		RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
 		ListCell	*lc;
+
 		Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+
+		/* If the partition has a single row, so does the parent. */
 		if (relation_is_onerow(childrel))
 		{
 			add_uniquekey_for_onerow(rel);
 			return;
 		}
 
+		/*
+		 * Inspect the unique keys one by one, try reusing them for the
+		 * parent relation.
+		 *
+		 * FIXME This needs more work to handle expressions and not just
+		 * simple Vars.
+		 */
 		foreach(lc, childrel->uniquekeys)
 		{
+			ListCell   *lc2;
+			List	   *parent_exprs = NIL;
+			bool		can_reuse = true;
+
 			UniqueKey *ukey = lfirst_node(UniqueKey, lc);
 			AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
-			List *parent_exprs = NIL;
-			bool can_reuse = true;
-			ListCell	*lc2;
+
+			/*
+			 * XXX Not sure what exactly we do here. Surely we deal with
+			 * expressions at child/parent level elsewhere? Can't we just
+			 * copy the code from there?
+			 */
 			foreach(lc2, ukey->exprs)
 			{
-				Var *var = (Var *)lfirst(lc2);
+				Var *var = (Var *) lfirst(lc2);
+
 				/*
-				 * If the expr comes from a expression, it is hard to build the expression
-				 * in parent so ignore that case for now.
+				 * XXX For now this only supports simple Var expressions,
+				 * so if there's a more complex expression we'll not copy
+				 * the unique key to the parent.
 				 */
 				if(!IsA(var, Var))
 				{
 					can_reuse = false;
 					break;
 				}
+
 				/* Convert it to parent var */
-				parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+				parent_exprs = lappend(parent_exprs,
+									   find_parent_var(appinfo, var));
 			}
-			if (can_reuse)
-				rel->uniquekeys = lappend(rel->uniquekeys,
-										  makeUniqueKey(parent_exprs,
-														ukey->multi_nullvals));
+
+			/* ignore unique keys with complex expressions */
+			if (!can_reuse)
+				continue;
+
+			rel->uniquekeys = lappend(rel->uniquekeys,
+									  makeUniqueKey(parent_exprs,
+													ukey->multi_nullvals));
 		}
+
+		return;
 	}
-	else
+
+	/*
+	 * A parent with multiple child relations. We only care about indexes that
+	 * are in all child relations, so we loop through indexes on the first one
+	 * and check that they exist in the other child relations too.
+	 */
+
+	childrel = linitial_node(RelOptInfo, childrels);
+	foreach(lc, childrel->indexlist)
 	{
-		/* Check for rule 2 */
-		childrel = linitial_node(RelOptInfo, childrels);
-		foreach(lc, childrel->indexlist)
-		{
-			IndexOptInfo *ind = lfirst(lc);
-			IndexOptInfo *modified_index;
-			if (!ind->unique || !ind->immediate ||
-				(ind->indpred != NIL && !ind->predOK))
-				continue;
+		IndexOptInfo *ind = lfirst(lc);
+		IndexOptInfo *modified_index;
 
-			/*
-			 * During simple_copy_indexinfo_to_parent, we need to convert var from
-			 * child var to parent var, index on expression is too complex to handle.
-			 * so ignore it for now.
-			 */
-			if (ind->indexprs != NIL)
-				continue;
+		/*
+		 * Ignore indexes that are not unique, immediately enforced. Partial
+		 * indexes with mismatched predicate are useless too.
+		 */
+		if (!ind->unique || !ind->immediate ||
+			(ind->indpred != NIL && !ind->predOK))
+			continue;
 
-			modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
-			/*
-			 * If the unique index doesn't contain partkey, then it is unique
-			 * on this partition only, so it is useless for us.
-			 */
-			if (!index_constains_partkey(rel, modified_index))
-				continue;
+		/*
+		 * During simple_copy_indexinfo_to_parent, we need to convert var from
+		 * child var to parent var, index on expression is too complex to handle.
+		 * so ignore it for now.
+		 *
+		 * FIXME We should support indexes on expressions.
+		 */
+		if (ind->indexprs != NIL)
+			continue;
 
-			global_uniq_indexlist = lappend(global_uniq_indexlist,  modified_index);
-		}
+		/*
+		 * Adopt the index definition for the parent.
+		 *
+		 * XXX This seems rather weird. We're constructing "artificial" index
+		 * for the partitioned table (kinda like a global index). Can't we
+		 * just have some simpler struct representing it?
+		 */
+		modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+
+		/*
+		 * If the unique index doesn't contain partkey, then it is unique
+		 * on this partition only, so it is useless for us.
+		 *
+		 * XXX Can't we do this check before simple_copy_indexinfo_to_parent?
+		 */
+		if (!index_constains_partkey(rel, modified_index))
+			continue;
 
-		if (global_uniq_indexlist != NIL)
+		global_uniq_indexlist = lappend(global_uniq_indexlist,  modified_index);
+	}
+
+	/* if there are no applicable unique indexes, we're done */
+	if (!global_uniq_indexlist)
+		return;
+
+	/*
+	 * We iterate over the child relations first, and inspect the unique
+	 * indexes for each hild, because this way we can stop early if we
+	 * happen to eliminate all the unique indexes.
+	 */
+	foreach(lc, childrels)
+	{
+		RelOptInfo *child = lfirst(lc);
+
+		/* skip the first index, which is where we got the list from */
+		if (is_first)
 		{
-			foreach(lc, childrels)
-			{
-				RelOptInfo *child = lfirst(lc);
-				if (is_first)
-				{
-					is_first = false;
-					continue;
-				}
-				adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
-			}
-			/* Now we have a list of unique index which are exactly same on all childrels,
-			 * Set the UniqueKey just like it is non-partition table
-			 */
-			populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+			is_first = false;
+			continue;
 		}
+
+		/* match the unique keys to indexes on this child */
+		adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+
+		/*
+		 * If we have eliminated all unique indexes, no point in looking at
+		 * the remaining child relations.
+		 */
+		if (!global_uniq_indexlist)
+			break;
 	}
+
+	/* Now we have a list of unique index which are exactly same on all child
+	 * relations. Set the UniqueKey just like it is non-partition table.
+	 */
+	populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
 }
 
 
 /*
  * populate_distinctrel_uniquekeys
+ *		Update unique keys for relation produced by DISTINCT.
+ *
+ * We can keep all unique keys from the input relations, because DISTINCT
+ * can only remove rows - it can't duplicate them. Also, the DISTINCT clause
+ * itself is a unique key, so add that.
  */
 void
 populate_distinctrel_uniquekeys(PlannerInfo *root,
@@ -292,11 +434,13 @@ populate_distinctrel_uniquekeys(PlannerInfo *root,
 {
 	/* The unique key before the distinct is still valid. */
 	distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+
 	add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
 }
 
 /*
  * populate_grouprel_uniquekeys
+ *		
  */
 void
 populate_grouprel_uniquekeys(PlannerInfo *root,
@@ -305,54 +449,76 @@ populate_grouprel_uniquekeys(PlannerInfo *root,
 
 {
 	Query *parse = root->parse;
-	bool input_ukey_added = false;
 	ListCell *lc;
 
+	/*
+	 * XXX Is this actually valid, before checking fro grouping sets?
+	 * The grouping sets may produce duplicate row even with just a single
+	 * input row, I think.
+	 */
 	if (relation_is_onerow(inputrel))
 	{
 		add_uniquekey_for_onerow(grouprel);
 		return;
 	}
+
+	/*
+	 * Bail out if there are grouping sets.
+	 *
+	 * XXX Could we maybe inspect the grouping sets and determine if this
+	 * generates distinct combinations? In some cases that's clearly not
+	 * the case (rollup, cube), but for some simple cases it might.
+	 */
 	if (parse->groupingSets)
 		return;
 
-	/* A Normal group by without grouping set. */
-	if (parse->groupClause)
+	/* It has aggregation but without a group by, so only one row returned */
+	if (!parse->groupClause)
+		add_uniquekey_for_onerow(grouprel);
+
+	/*
+	 * A regular group by, without grouping sets.
+	 *
+	 * Obviously, the whole group clause determines a unique key. But if
+	 * there are smaller unique keys on the input rel, we prefer those
+	 * because those are more flexible. If (a,b) is unique, (a,b,c) is
+	 * unique too. Only when there are no such smaller unique keys, we
+	 * add the unique key derived from the group clause.
+	 */
+	foreach(lc, inputrel->uniquekeys)
 	{
+		UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+
 		/*
-		 * Current even the groupby clause is Unique already, but if query has aggref
-		 * We have to create grouprel still. To keep the UnqiueKey short, we will check
-		 * the UniqueKey of input_rel still valid, if so we reuse it.
+		 * Ignore unique keys on the input that are not subset of the
+		 * group clause. We can't use incomplete unique keys.
 		 */
-		foreach(lc, inputrel->uniquekeys)
-		{
-			UniqueKey *ukey = lfirst_node(UniqueKey, lc);
-			if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
-			{
-				grouprel->uniquekeys = lappend(grouprel->uniquekeys,
-											   ukey);
-				input_ukey_added = true;
-			}
-		}
-		if (!input_ukey_added)
-			/*
-			 * group by clause must be a super-set of grouprel->reltarget->exprs except the
-			 * aggregation expr, so if such exprs is unique already, no bother to generate
-			 * new uniquekey for group by exprs.
-			 */
-			add_uniquekey_from_sortgroups(root,
-										  grouprel,
-										  root->parse->groupClause);
+		if (!list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+			continue;
+
+		grouprel->uniquekeys = lappend(grouprel->uniquekeys, ukey);
 	}
-	else
-		/* It has aggregation but without a group by, so only one row returned */
-		add_uniquekey_for_onerow(grouprel);
+
+	/*
+	 * Group clause must be a super-set of of grouprel->reltarget->exprs,
+	 * except for the aggregation expressions. So if we found a smaller
+	 * unique key on the input relation, don't bother adding a unique key
+	 * for the group clause.
+	 */
+	if (!grouprel->uniquekeys)
+		add_uniquekey_from_sortgroups(root,
+									  grouprel,
+									  root->parse->groupClause);
 }
 
 /*
  * simple_copy_uniquekeys
- * Using a function for the one-line code makes us easy to check where we simply
- * copied the uniquekey.
+ *		Copy yhe unique keys between relations.
+ *
+ * Using a function for the one-line code makes us easy to check where we
+ * simply copied the uniquekey.
+ *
+ * XXX Seems like an overkill, not sure what's the purpose?
  */
 void
 simple_copy_uniquekeys(RelOptInfo *oldrel,
@@ -362,24 +528,27 @@ simple_copy_uniquekeys(RelOptInfo *oldrel,
 }
 
 /*
- *  populate_unionrel_uniquekeys
+ * populate_unionrel_uniquekeys
+ *		Determine unique keys for UNION relation.
+ *
+ * XXX Does this need to care about UNION vs. UNION ALL? At least in the
+ * one-row code path?
  */
 void
 populate_unionrel_uniquekeys(PlannerInfo *root,
-							  RelOptInfo *unionrel)
+							 RelOptInfo *unionrel)
 {
-	ListCell	*lc;
-	List	*exprs = NIL;
+	ListCell   *lc;
+	List	   *exprs = NIL;
 
 	Assert(unionrel->uniquekeys == NIL);
 
+	/* XXX Why are we copying the expressions? */
 	foreach(lc, unionrel->reltarget->exprs)
-	{
 		exprs = lappend(exprs, lfirst(lc));
-	}
 
+	/* SQL: select union select; is valid, we need to handle it here. */
 	if (exprs == NIL)
-		/* SQL: select union select; is valid, we need to handle it here. */
 		add_uniquekey_for_onerow(unionrel);
 	else
 		unionrel->uniquekeys = lappend(unionrel->uniquekeys,
@@ -389,6 +558,7 @@ populate_unionrel_uniquekeys(PlannerInfo *root,
 
 /*
  * populate_joinrel_uniquekeys
+ *		Determine unique keys for a join relation.
  *
  * populate uniquekeys for joinrel. We will check each relation to see if its
  * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
@@ -404,70 +574,99 @@ populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
 							RelOptInfo *outerrel, RelOptInfo *innerrel,
 							List *restrictlist, JoinType jointype)
 {
-	ListCell *lc, *lc2;
-	List	*clause_list = NIL;
-	List	*outerrel_ukey_ctx;
-	List	*innerrel_ukey_ctx;
-	bool	inner_onerow, outer_onerow;
-	bool	mergejoin_allowed;
-
-	/* Care about the outerrel relation only for SEMI/ANTI join */
+	ListCell   *lc,
+			   *lc2;
+	List	   *clause_list = NIL;
+	List	   *outerrel_ukey_ctx;
+	List	   *innerrel_ukey_ctx;
+	bool		inner_onerow,
+				outer_onerow;
+	bool		mergejoin_allowed;
+
+	/* For SEMI/ANTI join, we care only about the outerrel unique keys. */
 	if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
 	{
 		foreach(lc, outerrel->uniquekeys)
 		{
 			UniqueKey	*uniquekey = lfirst_node(UniqueKey, lc);
+
+			/* Keep the unique key if it's included in the joinrel. */
 			if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
 				joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
 		}
+
 		return;
 	}
 
+	/* XXX What about JOIN_RIGHT? */
 	Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
 
-	/* Fast path */
+	/*
+	 * For regular joins, we need to combine unique keys from both sides
+	 * of the join, to get a new unique key for the join relation. So if
+	 * either side does not have a unique key, bail out.
+	 */
 	if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
 		return;
 
+	/* XXX maybe move to the if blocks? Not needed outside. */
 	inner_onerow = relation_is_onerow(innerrel);
 	outer_onerow = relation_is_onerow(outerrel);
 
-	outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
-	innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+	outerrel_ukey_ctx = initialize_uniquecontext_for_joinrel(outerrel);
+	innerrel_ukey_ctx = initialize_uniquecontext_for_joinrel(innerrel);
 
-	clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+	clause_list = select_mergejoin_clauses(root,
+										   joinrel, outerrel, innerrel,
 										   restrictlist, jointype,
 										   &mergejoin_allowed);
 
-	if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+	/*
+	 * XXX Seems a bit weird that it's called innerrel_keeps_unique but we
+	 * seem to use it in both directions. Or what's the "reverse" for? The
+	 * "reverse" name is not particularly descriptive.
+	 */
+	if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true))
 	{
-		bool outer_impact = jointype == JOIN_FULL;
+		bool	outer_impact = (jointype == JOIN_FULL);
+
+		/* Inspect unique keys on the outer relation. */
 		foreach(lc, outerrel_ukey_ctx)
 		{
 			UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
 
+			/*
+			 * If the output of the join does not include all the parts of the
+			 * unique key, it's useless, so mark it accordingly and ignore it.
+			 */
 			if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
 			{
 				ctx->useful = false;
 				continue;
 			}
 
-			/* Outer relation has one row, and the unique key is not duplicated after join,
-			 * the joinrel will still has one row unless the jointype == JOIN_FULL.
+			/*
+			 * When the outer relation has one row, and the unique key is not
+			 * duplicated after join, so the joinrel will still have just one
+			 * row unless the jointype == JOIN_FULL. In that case we're done,
+			 * it's the strictest unique key possible.
+			 *
+			 * If it's one-row with a JOIN_FULL, it might produce multiple
+			 * rows with NULLs, so set multi_nullvals. We also need to set
+			 * the exprs correctly since it can't be NIL any more.
+			 *
+			 * For other cases (not one-row relation), we just reuse the
+			 * unique key, but we may need to tweak the multi_nullvals.
 			 */
 			if (outer_onerow && !outer_impact)
 			{
 				add_uniquekey_for_onerow(joinrel);
 				return;
 			}
-			else if (outer_onerow)
+			else if (outer_onerow)	/* one-row and FULL join */
 			{
-				/*
-				 * The onerow outerrel becomes multi rows and multi_nullvals
-				 * will be changed to true. We also need to set the exprs correctly since it
-				 * can't be NIL any more.
-				 */
 				ListCell *lc2;
+
 				foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
 				{
 					joinrel->uniquekeys = lappend(joinrel->uniquekeys,
@@ -485,18 +684,38 @@ populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
 					joinrel->uniquekeys = lappend(joinrel->uniquekeys,
 												  ctx->uniquekey);
 			}
+
+			/*
+			 * Mark the unique key as added, so that we can ignore it later
+			 * when combining unique keys from both sides of the join.
+			 */
 			ctx->added_to_joinrel = true;
 		}
 	}
 
+	/*
+	 * XXX Seems this actually checks if "outerrel keeps unique" so the name
+	 * is misleading. Of maybe it's the previous block, not sure.
+	 *
+	 * XXX So why does this consider JOIN_FULL and JOIN_LEFT, while the previous
+	 * block only cares about JOIN_FULL?
+	 *
+	 * XXX This is almost exact copy of the previous block, so maybe make it
+	 * a separate function and just call it twice?
+	 */
 	if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
 	{
-		bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+		bool	outer_impact = (jointype == JOIN_FULL || jointype == JOIN_LEFT);
 
+		/* Inspect unique keys on the inner relation. */
 		foreach(lc, innerrel_ukey_ctx)
 		{
 			UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
 
+			/*
+			 * If the output of the join does not include all the parts of the
+			 * unique key, it's useless, so mark it accordingly and ignore it.
+			 */
 			if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
 			{
 				ctx->useful = false;
@@ -529,29 +748,52 @@ populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
 												  ctx->uniquekey);
 
 			}
+
+			/*
+			 * Mark the unique key as added, so that we can ignore it later
+			 * when combining unique keys from both sides of the join.
+			 */
 			ctx->added_to_joinrel = true;
 		}
 	}
 
 	/*
-	 * The combination of the UniqueKey from both sides is unique as well regardless
-	 * of join type, but no bother to add it if its subset has been added to joinrel
-	 * already or it is not useful for the joinrel.
+	 * XXX What if either of the previous two conditions did not match? In
+	 * that case we haven't updated the useful flag, and maybe the unique
+	 * key is not useful, but we don't know, right? So we should not be
+	 * using it in the next loop. Or maybe we should evaluate the flag
+	 * before the loops.
+	 */
+
+	/*
+	 * The combination of the UniqueKey from both sides is unique as well,
+	 * regardless of the join type. But don't bother to add it if its
+	 * subset has been added to joinrel already or when it's not useful for
+	 * the joinrel.
+	 *
+	 * XXX Maybe we should have a flag that both sides have useful keys?
+	 * Or maybe the loops are short/cheap?
 	 */
 	foreach(lc, outerrel_ukey_ctx)
 	{
 		UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+
+		/* when not useful or already added to the joinrel, skip it */
 		if (ctx1->added_to_joinrel || !ctx1->useful)
 			continue;
+
 		foreach(lc2, innerrel_ukey_ctx)
 		{
 			UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+
+			/* when not useful or already added to the joinrel, skip it */
 			if (ctx2->added_to_joinrel || !ctx2->useful)
 				continue;
+
+			/* If we add a onerow UniqueKey, we don't need another key. */
 			if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
 									   ctx1->uniquekey, ctx2->uniquekey,
 									   jointype))
-				/* If we set a onerow UniqueKey to joinrel, we don't need other. */
 				return;
 		}
 	}
@@ -560,8 +802,9 @@ populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
 
 /*
  * convert_subquery_uniquekeys
+ *		Covert the UniqueKey in subquery to outer relation.
  *
- * Covert the UniqueKey in subquery to outer relation.
+ * XXX Explain what exactly does the conversion do?
  */
 void convert_subquery_uniquekeys(PlannerInfo *root,
 								 RelOptInfo *currel,
@@ -618,12 +861,14 @@ void convert_subquery_uniquekeys(PlannerInfo *root,
 
 /*
  * innerrel_keeps_unique
+ *		Check if Unique key on the innerrel is valid after join.
  *
- * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
- * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
- * exists in clause_list.
+ * innerrel's UniqueKey will be still valid if innerrel's any-column mergeop
+ * outrerel's uniquekey exists in clause_list
  *
  * Note: the clause_list must be a list of mergeable restrictinfo already.
+ *
+ * XXX Misleading name? We seem to use it for "outerrel_keeps_unique" too.
  */
 static bool
 innerrel_keeps_unique(PlannerInfo *root,
@@ -634,26 +879,32 @@ innerrel_keeps_unique(PlannerInfo *root,
 {
 	ListCell	*lc, *lc2, *lc3;
 
+	/* XXX probably not needed, duplicate with the check in the caller
+	 * (populate_joinrel_uniquekeys). But it's cheap. */
 	if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
 		return false;
 
 	/* Check if there is outerrel's uniquekey in mergeable clause. */
 	foreach(lc, outerrel->uniquekeys)
 	{
-		List	*outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
-		bool clauselist_matchs_all_exprs = true;
+		List   *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+		bool	clauselist_matchs_all_exprs = true;
+
 		foreach(lc2, outer_uq_exprs)
 		{
 			Node *outer_uq_expr = lfirst(lc2);
 			bool find_uq_expr_in_clauselist = false;
+
 			foreach(lc3, clause_list)
 			{
 				RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
 				Node *outer_expr;
+
 				if (reverse)
 					outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
 				else
 					outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+
 				if (equal(outer_expr, outer_uq_expr))
 				{
 					find_uq_expr_in_clauselist = true;
@@ -677,22 +928,37 @@ innerrel_keeps_unique(PlannerInfo *root,
 
 /*
  * relation_is_onerow
- * Check if it is a one-row relation by checking UniqueKey.
+ *		Check if it is a one-row relation by checking UniqueKey.
+ *
+ * The one-row is a special case - there has to be just a single unique key,
+ * with no expressions.
  */
 bool
 relation_is_onerow(RelOptInfo *rel)
 {
 	UniqueKey *ukey;
-	if (rel->uniquekeys == NIL)
+
+	/* there has to be exactly one unique key */
+	if (list_length(rel->uniquekeys) != 1)
 		return false;
+
 	ukey = linitial_node(UniqueKey, rel->uniquekeys);
-	return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+
+	/* the unique key must have no expressions */
+	return (ukey->exprs == NIL);
 }
 
 /*
  * relation_has_uniquekeys_for
- *		Returns true if we have proofs that 'rel' cannot return multiple rows with
- *		the same values in each of 'exprs'.  Otherwise returns false.
+ *		Determines if the relation has unique key for a list of expressions.
+ *
+ * Returns true iff we can prove that the relation cannot return multiple rows
+ * with the same values in the provided expression.
+ *
+ * allow_multinulls determines whether we allow multiple NULL values or not.
+ *
+ * The special "one-row" unique key is considered incompatible with all
+ * possible expressions.
  */
 bool
 relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
@@ -710,20 +976,39 @@ relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
 	foreach(lc, rel->uniquekeys)
 	{
 		UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+
 		if (ukey->multi_nullvals && !allow_multinulls)
 			continue;
+
 		if (list_is_subset(ukey->exprs, exprs))
 			return true;
 	}
+
 	return false;
 }
 
 
 /*
  * get_exprs_from_uniqueindex
+ *		Return a list of expressions from a unique index.
+ *
+ * Provided with a list of expressions and opclass families, we try to match
+ * it to the index. If useful, we produce a list of index expressions (subset
+ * of the list we provided).
+ *
+ * We simply walk through the index expressions, and for each expression we
+ * check three things:
  *
- * Return a list of exprs which is unique. set useful to false if this
- * unique index is not useful for us.
+ * 1) If there's a matching (expr = Const) clause, we can simply ignore the
+ * expressions. Unique index on (a,b,c) guarantees uniqueness on (a,b) when
+ * there's condition (c=1).
+ *
+ * 2) Check that the index expression is present in the relation we're
+ * dealing with. If not, the unique key would be useless anyway, and the
+ * index can't produce unique key.
+ *
+ * XXX Shouldn't it be enough to return NULL when the index is not useful?
+ * The extra flag seems a bit unnecessary.
  */
 static List *
 get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
@@ -743,18 +1028,19 @@ get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
 	indexpr_item = list_head(unique_index->indexprs);
 	for(c = 0; c < unique_index->ncolumns; c++)
 	{
-		int attr = unique_index->indexkeys[c];
-		Expr *expr;
-		bool	matched_const = false;
-		ListCell	*lc1, *lc2;
+		int			attr = unique_index->indexkeys[c];
+		Expr	   *expr;
+		bool		matched_const = false;
+		ListCell   *lc1, *lc2;
 
-		if(attr > 0)
+		if (attr > 0)
 		{
+			/* regular attribute, just use the expression from index tlist */
 			expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
 		}
 		else if (attr == 0)
 		{
-			/* Expression index */
+			/* expression from the index */
 			expr = lfirst(indexpr_item);
 			indexpr_item = lnext(unique_index->indexprs, indexpr_item);
 		}
@@ -764,29 +1050,43 @@ get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
 			Assert(false);
 		}
 
+		/* should have a valid expression now */
+		Assert(expr);
+
 		/*
-		 * Check index_col = Const case with regarding to opfamily checking
-		 * If we can remove the index_col from the final UniqueKey->exprs.
+		 * Check if there's (index_col = Const) condition, and that it's using
+		 * a compatible opfamily. If yes, we can remove the index_col from the
+		 * final UniqueKey->exprs, because the value is constant (so removing
+		 * it can't introduce duplicities).
 		 */
 		forboth(lc1, const_exprs, lc2, const_expr_opfamilies)
 		{
-			if (list_member_oid((List *)lfirst(lc2), unique_index->opfamily[c])
-				&& match_index_to_operand((Node *) lfirst(lc1), c, unique_index))
+			List   *opfamilies = (List *) lfirst(lc2);
+			Node   *cexpr = (Node *) lfirst(lc1);
+
+			if (list_member_oid(opfamilies, unique_index->opfamily[c]) &&
+				match_index_to_operand(cexpr, c, unique_index))
 			{
 				matched_const = true;
 				break;
 			}
 		}
 
+		/* it's constant, so ignore the expression */
 		if (matched_const)
 			continue;
 
-		/* Check if the indexed expr is used in rel */
+		/*
+		 * Check if the indexed expr is used in rel. We do this after the
+		 * (col = Const) check, because nn expression may be in a a restrict
+		 * clause and not in the reltarget. So we don't want to rule out an
+		 * index unnecessarily.
+		 */
 		if (attr > 0)
 		{
 			/*
-			 * Normal Indexed column, if the col is not used, then the index is useless
-			 * for uniquekey.
+			 * Normal indexed column, if the col is not used, then the index
+			 * is useless for uniquekey.
 			 */
 			attr -= FirstLowInvalidHeapAttributeNumber;
 
@@ -806,67 +1106,85 @@ get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
 		/* check not null property. */
 		if (attr == 0)
 		{
-			/* We never know if a expression yields null or not */
+			/* We never know if an expression yields null or not */
 			*multi_nullvals = true;
 		}
-		else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
-				 && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
-								   unique_index->rel->notnullattrs))
+		else if (!bms_is_member(attr, unique_index->rel->notnullattrs) &&
+				 !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+								unique_index->rel->notnullattrs))
 		{
 			*multi_nullvals = true;
 		}
 
 		exprs = lappend(exprs, expr);
 	}
+
 	return exprs;
 }
 
 
 /*
  * add_uniquekey_for_onerow
- * If we are sure that the relation only returns one row, then all the columns
- * are unique. However we don't need to create UniqueKey for every column, we
- * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
- * since this one has strongest semantics.
+ *		Create a special unique key signifying that the rel has one row.
+ *
+ * If we are sure that the relation only returns one row (it might return
+ * no rows, but we still consider that unique), then all the columns are
+ * trivially unique.
+ *
+ * However we don't need to create UniqueKey with every column, we just
+ * set exprs = NIL, because that's easier to identify. We don't want to
+ * add unnecessary unique keys (such that we already have a unique key
+ * for a subset of the expressions), and with (exprs == NIL) we can just
+ * assume we have one unique key for each column in the rel.
+ *
+ * We discard all other unique keys, since it has the strongest semantics.
  */
 void
 add_uniquekey_for_onerow(RelOptInfo *rel)
 {
 	/*
-	 * We overwrite the previous UniqueKey on purpose since this one has the
-	 * strongest semantic.
+	 * We overwrite the previous UniqueKey on purpose since this one has
+	 * the strongest semantic (all other unique keys are implied by it).
 	 */
 	rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
 }
 
 
 /*
- * initililze_uniquecontext_for_joinrel
- * Return a List of UniqueKeyContext for an inputrel
+ * initialize_uniquecontext_for_joinrel
+ *		Return a List of UniqueKeyContext for an inputrel.
  */
 static List *
-initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+initialize_uniquecontext_for_joinrel(RelOptInfo *inputrel)
 {
-	List	*res = NIL;
-	ListCell *lc;
-	foreach(lc,  inputrel->uniquekeys)
+	List	   *res = NIL;
+	ListCell   *lc;
+
+	foreach(lc, inputrel->uniquekeys)
 	{
 		UniqueKeyContext context;
+
 		context = palloc(sizeof(struct UniqueKeyContextData));
 		context->uniquekey = lfirst_node(UniqueKey, lc);
 		context->added_to_joinrel = false;
 		context->useful = true;
+
 		res = lappend(res, context);
 	}
+
 	return res;
 }
 
-
 /*
  * get_exprs_from_uniquekey
- *	Unify the way of get List of exprs from a one-row UniqueKey or
- * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
- * UniqueKey. Return a List of exprs.
+ *		Extract expressions that are part of a unique key.
+ *
+ * The meaning of the result is a bit different in regular and one-row cases.
+ * For the regular case, the list of expressions form a single unique key,
+ * i.e. the combination of values is unique.
+ *
+ * For the one-row case, each individual expression is known to be unique
+ * (simply because in a single row everything is unique).
  *
  * rel1: The relation which you want to get the exprs.
  * ukey: The UniqueKey you want to get the exprs.
@@ -875,27 +1193,29 @@ static List *
 get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
 						 RelOptInfo *rel1, UniqueKey *ukey)
 {
-	ListCell *lc;
-	bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+	ListCell   *lc;
+	List	   *res = NIL;
+	bool		onerow = (rel1 != NULL) && relation_is_onerow(rel1);
 
-	List	*res = NIL;
+	/* We require at least one of those to be true. */
 	Assert(onerow || ukey);
-	if (onerow)
-	{
-		/* Only cares about the exprs still exist in joinrel */
-		foreach(lc, joinrel->reltarget->exprs)
-		{
-			Bitmapset *relids = pull_varnos(root, lfirst(lc));
-			if (bms_is_subset(relids, rel1->relids))
-			{
-				res = lappend(res, list_make1(lfirst(lc)));
-			}
-		}
-	}
-	else
+
+	/* if not a one-row unique key, just return the key's expressions */
+	if (!onerow)
+		return list_make1(ukey->exprs);
+
+	/*
+	 * If it's a one-row relation, we simply extract the expressions that
+	 * still exist in the reltarget.
+	 */
+	foreach(lc, joinrel->reltarget->exprs)
 	{
-		res = list_make1(ukey->exprs);
+		Bitmapset  *relids = pull_varnos(root, lfirst(lc));
+
+		if (bms_is_subset(relids, rel1->relids))
+			res = lappend(res, list_make1(lfirst(lc)));
 	}
+
 	return res;
 }
 
@@ -910,55 +1230,67 @@ get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
 
 /*
  * index_constains_partkey
- * return true if the index contains the partiton key.
+ *		Determines if the index includes a partition key.
+ *
+ * XXX Surely we already have a code doing this already? E.g. when creating
+ * a unique index on a partitioned table we define that.
  */
 static bool
-index_constains_partkey(RelOptInfo *partrel,  IndexOptInfo *ind)
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
 {
 	ListCell	*lc;
 	int	i;
+
 	Assert(IS_PARTITIONED_REL(partrel));
 	Assert(partrel->part_scheme->partnatts > 0);
 
 	for(i = 0; i < partrel->part_scheme->partnatts; i++)
 	{
-		Node *part_expr = linitial(partrel->partexprs[i]);
-		bool found_in_index = false;
+		Node   *part_expr = linitial(partrel->partexprs[i]);
+		bool	found_in_index = false;
+
 		foreach(lc, ind->indextlist)
 		{
-			Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+			Expr   *index_expr = lfirst_node(TargetEntry, lc)->expr;
+
 			if (equal(index_expr, part_expr))
 			{
 				found_in_index = true;
 				break;
 			}
 		}
+
 		if (!found_in_index)
 			return false;
 	}
+
 	return true;
 }
 
 /*
  * simple_indexinfo_equal
+ *		Compare two indexes to determine if they are the same.
+ *
+ * We need to do this because simple_copy_indexinfo_to_parent does change
+ * some elements. So this is not exactly the same as calling equal().
  *
- * Used to check if the 2 index is same as each other. The index here
- * is COPIED from childrel and did some tiny changes(see
- * simple_copy_indexinfo_to_parent)
+ * XXX I wonder if we could simply use equal(), somehow? In fact, we should
+ * probably build something much simpler than IndexOptInfo, just enough to
+ * do the checks.
  */
 static bool
 simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
 {
 	Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
 
-	return ind1->ncolumns == ind2->ncolumns &&
-		ind1->unique == ind2->unique &&
-		memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
-		memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
-		memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
-		memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
-		equal(get_tlist_exprs(ind1->indextlist, true),
-			  get_tlist_exprs(ind2->indextlist, true));
+	return ((ind1->ncolumns == ind2->ncolumns) &&
+			(ind1->unique == ind2->unique) &&
+			(memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0) &&
+			(memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0) &&
+			(memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0) &&
+			(memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0) &&
+			(equal(get_tlist_exprs(ind1->indextlist, true),
+				   get_tlist_exprs(ind2->indextlist, true))));
 }
 
 
@@ -981,11 +1313,21 @@ simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
 
 
 /*
- * simple_copy_indexinfo_to_parent (from partition)
- * Copy the IndexInfo from child relation to parent relation with some modification,
- * which is used to test:
- * 1. If the same index exists in all the childrels.
+ * simple_copy_indexinfo_to_parent
+ *		Copy index info from child to parent, with necessary tweaks.
+ *
+ * We use this copy to check:
+ *
+ * 1. If the same/matching index exists in all the childrels.
  * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ *
+ * XXX IMHO we should probably build something much simpler than a full
+ * IndexOptInfo copy, just enough to do the checks.
+ *
+ * XXX The fact that we copy so much data seems wrong, and having to
+ * define macros from copyfuncs.c seems like a very suspicious thing.
+ * One reason is that IndeOptInfo is fairly large struct, especially
+ * with all the fields, and we allocate it very often.
  */
 static IndexOptInfo *
 simple_copy_indexinfo_to_parent(PlannerInfo *root,
@@ -1027,20 +1369,24 @@ simple_copy_indexinfo_to_parent(PlannerInfo *root,
 
 /*
  * adjust_partition_unique_indexlist
+ *		Checks and eliminates indexes that do not exist on the child relation.
  *
- * global_unique_indexes: At the beginning, it contains the copy & modified
- * unique index from the first partition. And then check if each index in it still
- * exists in the following partitions. If no, remove it. at last, it has an
- * index list which exists in all the partitions.
+ * Walks the list of unique indexes, and eliminates those that don't match
+ * the child relation (i.e. where a matching child index does not exist).
+ * This is used to iteratively filter the list of candidate unique keys.
+ *
+ * After processing all child relations, the list contains only indexes that
+ * exist in all the child relations.
  */
 static void
 adjust_partition_unique_indexlist(PlannerInfo *root,
 								  RelOptInfo *parentrel,
 								  RelOptInfo *childrel,
-								  List **global_unique_indexes)
+								  List **indexes)
 {
 	ListCell	*lc, *lc2;
-	foreach(lc, *global_unique_indexes)
+
+	foreach(lc, *indexes)
 	{
 		IndexOptInfo	*g_ind = lfirst_node(IndexOptInfo, lc);
 		bool found_in_child = false;
@@ -1049,23 +1395,45 @@ adjust_partition_unique_indexlist(PlannerInfo *root,
 		{
 			IndexOptInfo   *p_ind = lfirst_node(IndexOptInfo, lc2);
 			IndexOptInfo   *p_ind_copy;
-			if (!p_ind->unique || !p_ind->immediate ||
-				(p_ind->indpred != NIL && !p_ind->predOK))
+
+			/*
+			 * Ignore child indexes that can't possibly match (not unique or
+			 * immediate, etc.)
+			 *
+			 * XXX We do these checks in many places, so maybe turn it into
+			 * a reusable macro?
+			 */
+			if ((!p_ind->unique) || (!p_ind->immediate) ||
+				(p_ind->indpred != NIL) && (!p_ind->predOK))
 				continue;
+
+			/*
+			 * XXX This seems possibly quite expensive. Imagine there are many
+			 * child relations, with a bunch of unique indexes each. Then this
+			 * generates a copy for each unique index in each child relation,
+			 * something like O(N^2/2) copies.
+			 */
 			p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+
+			/* Found a matching index for the child relation, we're done. */
 			if (simple_indexinfo_equal(p_ind_copy, g_ind))
 			{
 				found_in_child = true;
 				break;
 			}
 		}
+
+		/* No matching index in the child, so remove it from the list. */
 		if (!found_in_child)
-			/* The index doesn't exist in childrel, remove it from global_unique_indexes */
-			*global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+			*indexes = foreach_delete_current(*indexes, lc);
 	}
 }
 
-/* Helper function for groupres/distinctrel */
+/*
+ * Helper function for groupres/distinctrel
+ *
+ * FIXME Not sure about this.
+ */
 static void
 add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
 {
@@ -1073,27 +1441,32 @@ add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgrou
 	List	*exprs;
 
 	/*
-	 * XXX: If there are some vars which is not in current levelsup, the semantic is
-	 * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
-	 * check every level other than 0, if so, looks we have to write another
-	 * pull_var_walker.
+	 * XXX: If there are some vars which are not in the current levelsup, the
+	 * semantic is imprecise, should we avoid it or not? levelsup = 1 is just
+	 * a demo, maybe we need to check every level other than 0, if so, looks
+	 * we have to write another pull_var_walker.
 	 */
 	List	*upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
 
 	if (upper_vars != NIL)
 		return;
 
+	/* sortgroupclause can't be multi_nullvals */
 	exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
 	rel->uniquekeys = lappend(rel->uniquekeys,
-							  makeUniqueKey(exprs,
-											false /* sortgroupclause can't be multi_nullvals */));
+							  makeUniqueKey(exprs, false));
 }
 
 
 /*
  * add_combined_uniquekey
- * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
- * the jointype.
+ *		Add a unique key for a join, combined from keys on inner/outer side.
+ *
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no
+ * matter what's the exact jointype.
+ *
+ * Returns true if the unique key is "one-row" variant, so that the caller
+ * can stop considering further combinations.
  */
 bool
 add_combined_uniquekey(PlannerInfo *root,
@@ -1104,32 +1477,47 @@ add_combined_uniquekey(PlannerInfo *root,
 					   UniqueKey *inner_ukey,
 					   JoinType jointype)
 {
+	bool		multi_nullvals;
+	ListCell   *lc1, *lc2;
 
-	ListCell	*lc1, *lc2;
-
-	/* Either side has multi_nullvals or we have outer join,
-	 * the combined UniqueKey has multi_nullvals */
-	bool multi_nullvals = outer_ukey->multi_nullvals ||
+	/*
+	 * If either side has multi_nullvals, or we are dealing with an outer join,
+	 * the combined UniqueKey has multi_nullvals too.
+	 */
+	multi_nullvals = outer_ukey->multi_nullvals ||
 		inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
 
 	/* The only case we can get onerow joinrel after join */
-	if  (relation_is_onerow(outer_rel)
-		 && relation_is_onerow(inner_rel)
-		 && jointype == JOIN_INNER)
+	if (relation_is_onerow(outer_rel) &&
+		relation_is_onerow(inner_rel) &&
+		jointype == JOIN_INNER)
 	{
 		add_uniquekey_for_onerow(joinrel);
 		return true;
 	}
 
+	/*
+	 * XXX Isn't this wrong? Why is it combining expressions that are part
+	 * of the two unique keys? Imagine we have outer unique key on (a1, a2)
+	 * and inner outer key on (b1, b2). Then this adds four unique keys
+	 * for the join (a1,b1), (a1,b2), (a2,b1) and (a2,b2). Shouldn't it
+	 * just add (a1,a2,b1,b2)?
+	 */
 	foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
 	{
+		/*
+		 * XXX This calls get_exprs_from_uniquekey repeatedly for each outer
+		 * loop. Maybe we should calculate it just once before the loop.
+		 */
 		foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
 		{
 			List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+
 			joinrel->uniquekeys = lappend(joinrel->uniquekeys,
 										  makeUniqueKey(exprs,
 														multi_nullvals));
 		}
 	}
+
 	return false;
 }
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 8d8e493f5c..f29b65c07b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 		add_path(final_rel, path);
 	}
 
+	/* XXX comment? can we simply just copy the unique keys to the final relation? */
 	simple_copy_uniquekeys(current_rel, final_rel);
 
 	/*
@@ -3902,7 +3903,9 @@ create_grouping_paths(PlannerInfo *root,
 
 	set_cheapest(grouped_rel);
 
+	/* XXX does this apply to grouping sets too? */
 	populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
+
 	return grouped_rel;
 }
 
@@ -4625,7 +4628,10 @@ create_window_paths(PlannerInfo *root,
 
 	/* Now choose the best path(s) */
 	set_cheapest(window_rel);
+
+	/* XXX comment? */
 	simple_copy_uniquekeys(input_rel, window_rel);
+
 	return window_rel;
 }
 
@@ -4939,7 +4945,10 @@ create_distinct_paths(PlannerInfo *root,
 
 	/* Now choose the best path(s) */
 	set_cheapest(distinct_rel);
+
+	/* XXX comment */
 	populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
+
 	return distinct_rel;
 }
 
@@ -5200,6 +5209,7 @@ create_ordered_paths(PlannerInfo *root,
 	 */
 	Assert(ordered_rel->pathlist != NIL);
 
+	/* XXX comment */
 	simple_copy_uniquekeys(input_rel, ordered_rel);
 
 	return ordered_rel;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b7626545bf..72a3f3c598 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -691,6 +691,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
 
 	/* Add the UniqueKeys */
 	populate_unionrel_uniquekeys(root, result_rel);
+
 	return result_rel;
 }
 
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3eec1f4d74..c9829c5fc4 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -755,6 +755,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 											 pseudoconstant,
 											 rinfo->security_level,
 											 NULL, NULL, NULL);
+			/* XXX This is a bit weird, doing this outside make_restrictinfo */
 			child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
 			childquals = lappend(childquals, child_rinfo);
 			/* track minimum security level among child quals */
-- 
2.30.2


--------------F495F6B18672582269F00FF9
Content-Type: text/x-patch; charset=UTF-8;
 name="0005-Extend-UniqueKeys-20210317.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0005-Extend-UniqueKeys-20210317.patch"



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

* [PATCH 06/10] review
@ 2021-03-17 01:19  Tomas Vondra <tomas.vondra@postgresql.org>
  0 siblings, 0 replies; 190+ messages in thread

From: Tomas Vondra @ 2021-03-17 01:19 UTC (permalink / raw)

---
 src/backend/optimizer/path/uniquekeys.c | 2 ++
 src/backend/optimizer/plan/planner.c    | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
index b9567ca5c8..0e6c826fcd 100644
--- a/src/backend/optimizer/path/uniquekeys.c
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -1522,6 +1522,7 @@ add_combined_uniquekey(PlannerInfo *root,
 	return false;
 }
 
+/* FIXME comment */
 List*
 build_uniquekeys(PlannerInfo *root, List *sortclauses)
 {
@@ -1548,6 +1549,7 @@ build_uniquekeys(PlannerInfo *root, List *sortclauses)
 	return result;
 }
 
+/* FIXME comment */
 bool
 query_has_uniquekeys_for(PlannerInfo *root, List *pathuniquekeys,
 						 bool allow_multinulls)
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 62c0e46c49..f66b9d4898 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -4857,11 +4857,13 @@ create_distinct_paths(PlannerInfo *root,
 			Path	   *path = (Path *) lfirst(lc);
 
 			if (pathkeys_contained_in(needed_pathkeys, path->pathkeys))
+			{
 				add_path(distinct_rel, (Path *)
 						 create_upper_unique_path(root, distinct_rel,
 												  path,
 												  list_length(root->distinct_pathkeys),
 												  numDistinctRows));
+			}
 		}
 
 		foreach(lc, input_rel->unique_pathlist)
-- 
2.30.2


--------------F495F6B18672582269F00FF9
Content-Type: text/x-patch; charset=UTF-8;
 name="0007-Index-skip-scan-20210317.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0007-Index-skip-scan-20210317.patch"



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

* [PATCH 08/10] review
@ 2021-03-17 01:36  Tomas Vondra <tomas.vondra@postgresql.org>
  0 siblings, 0 replies; 190+ messages in thread

From: Tomas Vondra @ 2021-03-17 01:36 UTC (permalink / raw)

---
 src/backend/commands/explain.c       | 1 +
 src/backend/executor/nodeIndexscan.c | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index a160de5493..3b0b38099f 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1110,6 +1110,7 @@ ExplainIndexSkipScanKeys(int skipPrefixSize, ExplainState *es)
 {
 	if (skipPrefixSize > 0)
 	{
+		/* FIXME Why not to show this for TEXT output? */
 		if (es->format != EXPLAIN_FORMAT_TEXT)
 			ExplainPropertyInteger("Distinct Prefix", NULL, skipPrefixSize, es);
 	}
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 71aac4493d..720442b731 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -124,6 +124,8 @@ IndexNext(IndexScanState *node)
 
 		node->iss_ScanDesc = scandesc;
 
+		/* FIXME Is the else branch necessary? */
+
 		/* Index skip scan assumes xs_want_itup, so set it to true */
 		if (indexscan->indexskipprefixsize > 0)
 			node->iss_ScanDesc->xs_want_itup = true;
-- 
2.30.2


--------------F495F6B18672582269F00FF9
Content-Type: text/x-patch; charset=UTF-8;
 name="0009-Btree-implementation-of-skipping-20210317.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0009-Btree-implementation-of-skipping-20210317.patch"



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

* [PATCH 2/2] review
@ 2025-04-03 18:14  Álvaro Herrera <alvherre@alvh.no-ip.org>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2025-04-03 18:14 UTC (permalink / raw)

---
 contrib/pgcrypto/crypt-gensalt.c |  26 ++--
 contrib/pgcrypto/crypt-sha.c     | 248 ++++++++++++-------------------
 contrib/pgcrypto/px-crypt.c      |  14 +-
 contrib/pgcrypto/px-crypt.h      |   6 +-
 4 files changed, 121 insertions(+), 173 deletions(-)

diff --git a/contrib/pgcrypto/crypt-gensalt.c b/contrib/pgcrypto/crypt-gensalt.c
index 922f9eea6fa..ef68ae0e49e 100644
--- a/contrib/pgcrypto/crypt-gensalt.c
+++ b/contrib/pgcrypto/crypt-gensalt.c
@@ -186,6 +186,9 @@ _crypt_gensalt_blowfish_rn(unsigned long count,
 	return output;
 }
 
+/*
+ * Helper for _crypt_gensalt_sha256_rn and _crypt_gensalt_sha512_rn
+ */
 static char *
 _crypt_gensalt_sha(unsigned long count,
 				   const char *input, int size, char *output, int output_size)
@@ -196,32 +199,25 @@ _crypt_gensalt_sha(unsigned long count,
 
 	/* output buffer must be allocated with PX_MAX_SALT_LEN bytes */
 	if (PX_MAX_SALT_LEN < result_bufsize)
-	{
 		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("invalid size of salt")));
-	}
+				errcode(ERRCODE_SYNTAX_ERROR),
+				errmsg("invalid size of salt"));
 
 	/*
 	 * Care must be taken to not exceed the buffer size allocated for the
 	 * input character buffer.
 	 */
-	if ((PX_SHACRYPT_SALT_MAX_LEN != size)
-		|| (output_size < size))
-	{
+	if ((PX_SHACRYPT_SALT_MAX_LEN != size) || (output_size < size))
 		ereport(ERROR,
-				(errcode(ERRCODE_INTERNAL_ERROR),
-				 errmsg("invalid length of salt buffer")));
-	}
+				errcode(ERRCODE_INTERNAL_ERROR),
+				errmsg("invalid length of salt buffer"));
 
 	/* Skip magic bytes, set by callers */
 	s_ptr += 3;
 	if ((rc = pg_snprintf(s_ptr, 18, "rounds=%ld$", count)) <= 0)
-	{
 		ereport(ERROR,
-				(errcode(ERRCODE_INTERNAL_ERROR),
-				 errmsg("cannot format salt string")));
-	}
+				errcode(ERRCODE_INTERNAL_ERROR),
+				errmsg("cannot format salt string"));
 
 	/* s_ptr should now be positioned at the start of the salt string */
 	s_ptr += rc;
@@ -242,6 +238,7 @@ _crypt_gensalt_sha(unsigned long count,
 	return output;
 }
 
+/* gen_list->gen function for sha512 */
 char *
 _crypt_gensalt_sha512_rn(unsigned long count,
 						 char const *input, int size,
@@ -256,6 +253,7 @@ _crypt_gensalt_sha512_rn(unsigned long count,
 	return _crypt_gensalt_sha(count, input, size, output, output_size);
 }
 
+/* gen_list->gen function for sha256 */
 char *
 _crypt_gensalt_sha256_rn(unsigned long count,
 						 const char *input, int size,
diff --git a/contrib/pgcrypto/crypt-sha.c b/contrib/pgcrypto/crypt-sha.c
index 13ee8913052..862f3fb9a68 100644
--- a/contrib/pgcrypto/crypt-sha.c
+++ b/contrib/pgcrypto/crypt-sha.c
@@ -44,6 +44,8 @@
  *
  */
 #include "postgres.h"
+
+#include "common/string.h"
 #include "miscadmin.h"
 
 #include "px-crypt.h"
@@ -78,23 +80,20 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 
 	const char *dec_salt_binary;	/* pointer into the real salt string */
 	StringInfo	decoded_salt = NULL;	/* decoded salt string */
-
 	unsigned char sha_buf[PX_SHACRYPT_DIGEST_MAX_LEN];
-	unsigned char sha_buf_tmp[PX_SHACRYPT_DIGEST_MAX_LEN];	/* temporary buffer for
-															 * digests */
 
+	/* temporary buffer for digests */
+	unsigned char sha_buf_tmp[PX_SHACRYPT_DIGEST_MAX_LEN];
 	char		rounds_custom = 0;
 	char	   *p_bytes = NULL;
 	char	   *s_bytes = NULL;
 	char	   *cp = NULL;
 	const char *fp = NULL;		/* intermediate pointer within salt string */
 	const char *ep = NULL;		/* holds pointer to the end of the salt string */
-
 	size_t		buf_size = 0;	/* buffer size for sha256crypt/sha512crypt */
 	unsigned int block;			/* number of bytes processed */
-	unsigned long rounds = PX_SHACRYPT_ROUNDS_DEFAULT;
-
-	unsigned	len,
+	uint32		rounds = PX_SHACRYPT_ROUNDS_DEFAULT;
+	unsigned int len,
 				salt_len = 0;
 
 	/* Init result buffer */
@@ -106,22 +105,16 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		return NULL;
 
 	if (pw == NULL)
-	{
 		elog(ERROR, "null value for password rejected");
-	}
 
 	if (salt == NULL)
-	{
 		elog(ERROR, "null value for salt rejected");
-	}
 
 	/*
 	 * Make sure result buffers are large enough.
 	 */
 	if (dstlen < PX_SHACRYPT_BUF_LEN)
-	{
 		elog(ERROR, "insufficient result buffer size to encrypt password");
-	}
 
 	/* Init contents of buffers properly */
 	memset(&sha_buf, '\0', sizeof(sha_buf));
@@ -138,27 +131,22 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	 * Analyze and prepare the salt string
 	 *
 	 * The magic string should be specified in the first three bytes of the
-	 * salt string. But do some sanity checks before.
+	 * salt string.  Do some sanity checks first.
 	 */
 	if (strlen(dec_salt_binary) < 3)
-	{
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("invalid salt")));
-	}
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("invalid salt"));
 
 	/*
 	 * Check format of magic bytes. These should define either 5=sha256crypt
 	 * or 6=sha512crypt in the second byte, enclosed by ascii dollar signs.
 	 */
-	if ((dec_salt_binary[0] != '$')
-		&& (dec_salt_binary[2] != '$'))
-	{
+	if ((dec_salt_binary[0] != '$') || (dec_salt_binary[2] != '$'))
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("invalid format of salt")),
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("invalid format of salt"),
 				errhint("magic byte format for shacrypt is either \"$5$\" or \"$6$\""));
-	}
 
 	/*
 	 * Check magic byte for supported shacrypt digest.
@@ -190,62 +178,54 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	 * string, we don't treat this special: This is just absorbed as part of
 	 * the salt with up to PX_SHACRYPT_SALT_LEN_MAX.
 	 *
-	 * Unknown magic byte is handled later below
+	 * Unknown magic byte is handled further below.
 	 */
 	if (strncmp(dec_salt_binary,
 				rounds_prefix, sizeof(rounds_prefix) - 1) == 0)
 	{
-
 		const char *num = dec_salt_binary + sizeof(rounds_prefix) - 1;
 		char	   *endp;
-		long		srounds = strtoul(num, &endp, 10);
+		int			srounds = strtoint(num, &endp, 10);
 
-		if (*endp == '$')
-		{
-			dec_salt_binary = endp + 1;
-
-			/*
-			 * We violate supported lower or upper bound of rounds, but in
-			 * this case we change this value to the supported lower or upper
-			 * value. We don't do this silently and print a NOTICE in such a
-			 * case.
-			 *
-			 * Note that a salt string generated with gen_salt() would never
-			 * generated such a salt string, since it would error out.
-			 *
-			 * But Drepper's upstream reference implementation supports this
-			 * when passing the salt string directly, so we maintain
-			 * compatibility here.
-			 */
-			if (srounds > PX_SHACRYPT_ROUNDS_MAX)
-			{
-				ereport(NOTICE,
-						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-						errmsg("rounds=%ld exceeds maximum supported value (%ld), using %ld instead",
-							   srounds, PX_SHACRYPT_ROUNDS_MAX,
-							   PX_SHACRYPT_ROUNDS_MAX));
-				srounds = PX_SHACRYPT_ROUNDS_MAX;
-			}
-			else if (srounds < PX_SHACRYPT_ROUNDS_MIN)
-			{
-				ereport(NOTICE,
-						errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-						errmsg("rounds=%ld is below supported value (%ld), using %ld instead",
-							   srounds, PX_SHACRYPT_ROUNDS_MIN,
-							   PX_SHACRYPT_ROUNDS_MIN));
-				srounds = PX_SHACRYPT_ROUNDS_MIN;
-			}
-
-			rounds = (unsigned long) srounds;
-			rounds_custom = 1;
-		}
-		else
-		{
+		if (*endp != '$')
 			ereport(ERROR,
 					errcode(ERRCODE_SYNTAX_ERROR),
 					errmsg("could not parse salt options"));
+
+		dec_salt_binary = endp + 1;
+
+		/*
+		 * We violate supported lower or upper bound of rounds, but in this
+		 * case we change this value to the supported lower or upper value. We
+		 * don't do this silently and print a NOTICE in such a case.
+		 *
+		 * Note that a salt string generated with gen_salt() would never
+		 * generated such a salt string, since it would error out.
+		 *
+		 * But Drepper's upstream reference implementation supports this when
+		 * passing the salt string directly, so we maintain compatibility.
+		 */
+		if (srounds > PX_SHACRYPT_ROUNDS_MAX)
+		{
+			ereport(NOTICE,
+					errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+					errmsg("rounds=%d exceeds maximum supported value (%d), using %d instead",
+						   srounds, PX_SHACRYPT_ROUNDS_MAX,
+						   PX_SHACRYPT_ROUNDS_MAX));
+			srounds = PX_SHACRYPT_ROUNDS_MAX;
+		}
+		else if (srounds < PX_SHACRYPT_ROUNDS_MIN)
+		{
+			ereport(NOTICE,
+					errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+					errmsg("rounds=%d is below supported value (%d), using %d instead",
+						   srounds, PX_SHACRYPT_ROUNDS_MIN,
+						   PX_SHACRYPT_ROUNDS_MIN));
+			srounds = PX_SHACRYPT_ROUNDS_MIN;
 		}
 
+		rounds = (uint32) srounds;
+		rounds_custom = 1;
 	}
 
 	/*
@@ -294,9 +274,7 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	}
 
 	if (rounds_custom > 0)
-	{
-		appendStringInfo(out_buf, "rounds=%lu$", rounds);
-	}
+		appendStringInfo(out_buf, "rounds=%u$", rounds);
 
 	/*
 	 * We need the real decoded salt string from salt input, this is every
@@ -326,47 +304,34 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		 * this point.
 		 */
 		fp = strstr(dec_salt_binary, magic_bytes[0]);
-
 		if (fp != NULL)
-		{
 			elog(ERROR, "bogus magic byte found in salt string");
-		}
 
 		fp = strstr(dec_salt_binary, magic_bytes[1]);
-
 		if (fp != NULL)
-		{
 			elog(ERROR, "bogus magic byte found in salt string");
-		}
 
 		/*
 		 * This looks very strict, but we assume the caller did something
 		 * wrong when we see a "rounds=" option here.
 		 */
 		fp = strstr(dec_salt_binary, rounds_prefix);
-
 		if (fp != NULL)
-		{
 			elog(ERROR, "invalid rounds option specified in salt string");
-		}
 
 		if (*ep != '$')
 		{
 			if (isalpha(*ep) || isdigit(*ep) || (*ep == '.') || (*ep == '/'))
-			{
 				appendStringInfoCharMacro(decoded_salt, *ep);
-			}
 			else
-			{
 				elog(ERROR, "invalid character in salt string: \"%c\"", *ep);
-			}
 		}
 		else
 		{
 			/*
 			 * We encountered a '$' marker. Check if we already absorbed some
 			 * bytes from input. If true, we are optimistic and terminate at
-			 * this stage. Of not, we try further.
+			 * this stage. If not, we try further.
 			 *
 			 * If we already consumed enough bytes for the salt string,
 			 * everything that is after this marker is considered to be part
@@ -375,23 +340,19 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 			if (decoded_salt->len > 0)
 				break;
 		}
-
 	}
 
 	salt_len = decoded_salt->len;
 	appendStringInfoString(out_buf, decoded_salt->data);
-	elog(DEBUG1, "using salt \"%s\", salt len = %d, rounds = %lu",
+	elog(DEBUG1, "using salt \"%s\", salt len = %d, rounds = %u",
 		 decoded_salt->data, decoded_salt->len, rounds);
 
 	/*
-	 * Sanity check:
-	 *
-	 * At this point the salt string buffer must not exceed expected size
+	 * Sanity check: at this point the salt string buffer must not exceed
+	 * expected size.
 	 */
 	if (out_buf->len > (3 + 17 * rounds_custom + salt_len))
-	{
 		elog(ERROR, "unexpected length of salt string");
-	}
 
 	/*-
 	 * 1. Start digest A
@@ -413,29 +374,26 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	px_md_update(digestB, (const unsigned char *) pw, len);
 	px_md_finish(digestB, sha_buf);
 
-	/*-
-	 * 9. For each block of (excluding the NULL byte), add
-	 *    digest B to digest A.
+	/*
+	 * 9. For each block (excluding the NULL byte), add digest B to digest A.
 	 */
 	for (block = len; block > buf_size; block -= buf_size)
-	{
 		px_md_update(digestA, sha_buf, buf_size);
-	}
 
 	/*-
-	 * 10 For the remaining N bytes of the password string, add
-	 * the first N bytes of digest B to A */
+	 * 10. For the remaining N bytes of the password string, add the first N
+	 * bytes of digest B to A.
+	 */
 	px_md_update(digestA, sha_buf, block);
 
 	/*-
-	 * 11 For each bit of the binary representation of the length of the
-	 * password string up to and including the highest 1-digit, starting
-	 * from to lowest bit position (numeric value 1)
+	 * 11. For each bit of the binary representation of the length of the
+	 * password string up to and including the highest 1-digit, starting from
+	 * to lowest bit position (numeric value 1)
 	 *
 	 * a) for a 1-digit add digest B (sha_buf) to digest A
 	 * b) for a 0-digit add the password string
 	 */
-
 	block = len;
 	while (block)
 	{
@@ -447,10 +405,10 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		block >>= 1;
 	}
 
-	/* 12 Finalize digest A */
+	/* 12. Finalize digest A */
 	px_md_finish(digestA, sha_buf);
 
-	/* 13 Start digest DP */
+	/* 13. Start digest DP */
 	px_md_reset(digestB);
 
 	/*-
@@ -458,20 +416,17 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	 * to the digest DP
 	 */
 	for (block = len; block > 0; block--)
-	{
 		px_md_update(digestB, (const unsigned char *) pw, len);
-	}
 
-	/* 15 Finalize digest DP */
+	/* 15. Finalize digest DP */
 	px_md_finish(digestB, sha_buf_tmp);
 
 	/*-
-	 * 16 produce byte sequence P with same length as password.
-	 *
+	 * 16. produce byte sequence P with same length as password.
 	 *     a) for each block of 32 or 64 bytes of length of the password
 	 *        string the entire digest DP is used
 	 *     b) for the remaining N (up to  31 or 63) bytes use the
-	 *         first N bytes of digest DP
+	 *        first N bytes of digest DP
 	 */
 	if ((p_bytes = palloc0(len)) == NULL)
 	{
@@ -484,30 +439,28 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	memcpy(cp, sha_buf_tmp, block);
 
 	/*
-	 * 17 Start digest DS
+	 * 17. Start digest DS
 	 */
 	px_md_reset(digestB);
 
 	/*-
-	 * 18 Repeat the following 16+A[0] times, where A[0] represents the first
+	 * 18. Repeat the following 16+A[0] times, where A[0] represents the first
 	 *    byte in digest A interpreted as an 8-bit unsigned value
 	 *    add the salt to digest DS
 	 */
 	for (block = 16 + sha_buf[0]; block > 0; block--)
-	{
 		px_md_update(digestB, (const unsigned char *) dec_salt_binary, salt_len);
-	}
 
 	/*
-	 * 19 Finalize digest DS
+	 * 19. Finalize digest DS
 	 */
 	px_md_finish(digestB, sha_buf_tmp);
 
 	/*-
-	 * 20 Produce byte sequence S of the same length as the salt string where
+	 * 20. Produce byte sequence S of the same length as the salt string where
 	 *
 	 * a) for each block of 32 or 64 bytes of length of the salt string the
-	 *     entire digest DS is used
+	 *    entire digest DS is used
 	 *
 	 * b) for the remaining N (up to  31 or 63) bytes use the first N
 	 *    bytes of digest DS
@@ -516,27 +469,24 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		goto error;
 
 	for (cp = s_bytes, block = salt_len; block > buf_size; block -= buf_size, cp += buf_size)
-	{
 		memcpy(cp, sha_buf_tmp, buf_size);
-	}
 	memcpy(cp, sha_buf_tmp, block);
 
 	/* Make sure we don't leave something important behind */
 	px_memset(&sha_buf_tmp, 0, sizeof sha_buf);
 
 	/*-
-	 * 21 Repeat a loop according to the number specified in the rounds=<N>
-	 *    specification in the salt (or the default value if none is
-	 *    present).  Each round is numbered, starting with 0 and up to N-1.
+	 * 21. Repeat a loop according to the number specified in the rounds=<N>
+	 *     specification in the salt (or the default value if none is
+	 *     present).  Each round is numbered, starting with 0 and up to N-1.
 	 *
-	 *    The loop uses a digest as input.  In the first round it is the
-	 *    digest produced in step 12.  In the latter steps it is the digest
-	 *    produced in step 21.h of the previous round.  The following text
-	 *    uses the notation "digest A/B" to describe this behavior.
+	 *     The loop uses a digest as input.  In the first round it is the
+	 *     digest produced in step 12.  In the latter steps it is the digest
+	 *     produced in step 21.h of the previous round.  The following text
+	 *     uses the notation "digest A/B" to describe this behavior.
 	 */
 	for (block = 0; block < rounds; block++)
 	{
-
 		/*
 		 * Make it possible to abort in case large values for "rounds" are
 		 * specified.
@@ -546,29 +496,25 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		/* a) start digest B */
 		px_md_reset(digestB);
 
-		/*
-		 * b) for odd round numbers add the byte sequense P to digest B c) for
-		 * even round numbers add digest A/B
+		/*-
+		 * b) for odd round numbers add the byte sequence P to digest B
+		 * c) for even round numbers add digest A/B
 		 */
 		px_md_update(digestB,
 					 (block & 1) ? (const unsigned char *) p_bytes : sha_buf,
 					 (block & 1) ? len : buf_size);
 
 		/* d) for all round numbers not divisible by 3 add the byte sequence S */
-		if (block % 3)
-		{
+		if ((block % 3) != 0)
 			px_md_update(digestB, (const unsigned char *) s_bytes, salt_len);
-		}
 
 		/* e) for all round numbers not divisible by 7 add the byte sequence P */
-		if (block % 7)
-		{
+		if ((block % 7) != 0)
 			px_md_update(digestB, (const unsigned char *) p_bytes, len);
-		}
 
-		/*
-		 * f) for odd round numbers add digest A/C g) for even round numbers
-		 * add the byte sequence P
+		/*-
+		 * f) for odd round numbers add digest A/C
+		 * g) for even round numbers add the byte sequence P
 		 */
 		px_md_update(digestB,
 					 (block & 1) ? sha_buf : (const unsigned char *) p_bytes,
@@ -576,7 +522,6 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 
 		/* h) finish digest C. */
 		px_md_finish(digestB, sha_buf);
-
 	}
 
 	px_md_free(digestA);
@@ -594,15 +539,15 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 	/* prepare final result buffer */
 	appendStringInfoCharMacro(out_buf, '$');
 
-#define b64_from_24bit(B2, B1, B0, N)                      \
-	do {                                                    \
-		unsigned int w = ((B2) << 16) | ((B1) << 8) | (B0); \
-		int i = (N);                                        \
-		while (i-- > 0)                                     \
-		{                                                   \
+#define b64_from_24bit(B2, B1, B0, N)                                    \
+	do {                                                                 \
+		unsigned int w = ((B2) << 16) | ((B1) << 8) | (B0);              \
+		int		i = (N);                                                 \
+		while (i-- > 0)                                                  \
+		{                                                                \
 			appendStringInfoCharMacro(out_buf, _crypt_itoa64[w & 0x3f]); \
-			w >>= 6;                                        \
-		}                                                   \
+			w >>= 6;                                                     \
+		}                                                                \
 	} while (0)
 
 	switch (type)
@@ -655,7 +600,6 @@ px_crypt_shacrypt(const char *pw, const char *salt, char *passwd, unsigned dstle
 		case PGCRYPTO_SHA_UNKOWN:
 			/* we shouldn't land here ... */
 			elog(ERROR, "unsupported digest length");
-
 	}
 
 	*cp = '\0';
@@ -690,7 +634,7 @@ error:
 		destroyStringInfo(out_buf);
 
 	ereport(ERROR,
-			(errcode(ERRCODE_INTERNAL_ERROR),
-			 errmsg("cannot create encrypted password")));
+			errcode(ERRCODE_INTERNAL_ERROR),
+			errmsg("cannot create encrypted password"));
 	return NULL;				/* keep compiler quiet */
 }
diff --git a/contrib/pgcrypto/px-crypt.c b/contrib/pgcrypto/px-crypt.c
index a786277aeac..d7729eec9bc 100644
--- a/contrib/pgcrypto/px-crypt.c
+++ b/contrib/pgcrypto/px-crypt.c
@@ -139,10 +139,16 @@ static struct generator gen_list[] = {
 	{"md5", _crypt_gensalt_md5_rn, 6, 0, 0, 0},
 	{"xdes", _crypt_gensalt_extended_rn, 3, PX_XDES_ROUNDS, 1, 0xFFFFFF},
 	{"bf", _crypt_gensalt_blowfish_rn, 16, PX_BF_ROUNDS, 4, 31},
-	{"sha256crypt", _crypt_gensalt_sha256_rn, PX_SHACRYPT_SALT_MAX_LEN,
-	PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX},
-	{"sha512crypt", _crypt_gensalt_sha512_rn, PX_SHACRYPT_SALT_MAX_LEN,
-	PX_SHACRYPT_ROUNDS_DEFAULT, PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX},
+	{
+		"sha256crypt", _crypt_gensalt_sha256_rn,
+		PX_SHACRYPT_SALT_MAX_LEN, PX_SHACRYPT_ROUNDS_DEFAULT,
+		PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX
+	},
+	{
+		"sha512crypt", _crypt_gensalt_sha512_rn,
+		PX_SHACRYPT_SALT_MAX_LEN, PX_SHACRYPT_ROUNDS_DEFAULT,
+		PX_SHACRYPT_ROUNDS_MIN, PX_SHACRYPT_ROUNDS_MAX
+	},
 	{NULL, NULL, 0, 0, 0, 0}
 };
 
diff --git a/contrib/pgcrypto/px-crypt.h b/contrib/pgcrypto/px-crypt.h
index 3829ec6c347..bb03cf5592a 100644
--- a/contrib/pgcrypto/px-crypt.h
+++ b/contrib/pgcrypto/px-crypt.h
@@ -61,13 +61,13 @@
 #define PX_SHACRYPT_BUF_LEN (PX_SHACRYPT_SALT_BUF_LEN + 86 + 1)
 
 /* Default number of rounds of shacrypt if not explicitly specified.  */
-#define PX_SHACRYPT_ROUNDS_DEFAULT 5000l
+#define PX_SHACRYPT_ROUNDS_DEFAULT 5000
 
 /* Minimum number of rounds of shacrypt.  */
-#define PX_SHACRYPT_ROUNDS_MIN 1000l
+#define PX_SHACRYPT_ROUNDS_MIN 1000
 
 /* Maximum number of rounds of shacrypt.  */
-#define PX_SHACRYPT_ROUNDS_MAX 999999999l
+#define PX_SHACRYPT_ROUNDS_MAX 999999999
 
 /*
  * main interface
-- 
2.39.5


--zih5rdnpxe7pfzuf--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH v10 2/2] review
@ 2026-02-06 09:24  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 190+ messages in thread

From: Álvaro Herrera @ 2026-02-06 09:24 UTC (permalink / raw)

---
 doc/src/sgml/config.sgml                |  18 ++--
 src/backend/commands/variable.c         | 127 +++++++++++-------------
 src/backend/postmaster/launch_backend.c |   2 +-
 src/backend/utils/init/miscinit.c       |   2 +-
 src/backend/utils/misc/guc_tables.c     |  14 +--
 src/include/postmaster/proctypelist.h   |  38 +++----
 src/test/regress/expected/guc.out       |   8 +-
 7 files changed, 93 insertions(+), 116 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index cbc748a3636..984194ca758 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7090,15 +7090,15 @@ local0.*    /var/log/postgresql
       </term>
       <listitem>
        <para>
-        Controls which <link linkend="runtime-config-severity-levels">message
-        levels</link> are written to the server log.
-        Valid values are a comma-separated list of <literal>type:level</literal>
-        and a single <literal>level</literal>. The list allows it to use
-        different levels per process type. Only the single <literal>level</literal>
-        is mandatory (order does not matter) and it is assigned to the process
-        types that are not specified in the list.
-        Valid process types are listed in the table below, each corresponding to
-        either postmaster, an auxiliary process type or a backend.
+        Controls which
+        <link linkend="runtime-config-severity-levels">message levels</link>
+        are written to the server log.  Valid values are a comma-separated
+        list of zero or more
+        <literal><replaceable>process type</replaceable>:<replaceable>level</replaceable></literal>
+        entries and exactly one mandatory
+        <literal><replaceable>level</replaceable></literal> entry,
+        which is the default for process types not listed.
+        Valid process types are listed in the table below.
         <simplelist type="vert" columns="4">
          <member><literal>archiver</literal></member>
          <member><literal>autovacuum</literal></member>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a450254d1fa..97b9a6b5964 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1279,12 +1279,12 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 /*
  * GUC check_hook for log_min_messages
  *
- * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
- * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
- * single LEVEL element should be part of this list and it is applied as a
- * final step to the process types that are not specified. For backward
- * compatibility, the old syntax is still accepted and it means to apply the
- * LEVEL for all process types.
+ * This value is parsed as a comma-separated list of zero or more TYPE:LEVEL
+ * elements.  For each element, TYPE corresponds to a bk_category value (see
+ * postmaster/proctypelist.h), and LEVEL is one of log_min_message_lvls.
+ *
+ * In addition, there must be a single LEVEL element (with no TYPE part)
+ * which sets the default level for process types that aren't specified.
  */
 bool
 check_log_min_messages(char **newval, void **extra, GucSource source)
@@ -1293,22 +1293,21 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	List	   *elemlist;
 	StringInfoData buf;
 	char	   *result;
-	bool		first = true;
 	int			newlevel[BACKEND_NUM_TYPES];
-	bool		assigned[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES] = {0};
 	int			genericlevel = -1;	/* -1 means not assigned */
 
-	/* Initialize the array. */
-	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
-	{
-		newlevel[i] = WARNING;
-		assigned[i] = false;
-	}
+	const char *const process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+		[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+	};
 
 	/* Need a modifiable copy of string. */
 	rawstring = guc_strdup(LOG, *newval);
 
-	/* Parse string into list of identifiers. */
+	/* Parse the string into a list. */
 	if (!SplitGUCList(rawstring, ',', &elemlist))
 	{
 		/* syntax error in list */
@@ -1321,27 +1320,22 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	/* Validate and assign log level and process type. */
 	foreach_ptr(char, tok, elemlist)
 	{
-		char	   *sep;
-		const struct config_enum_entry *entry;
+		char	   *sep = strchr(tok, ':');
 
 		/*
-		 * Check whether there is a process type following the log level. If
-		 * there is no separator, it means this is the generic log level. The
-		 * generic log level will be assigned to the process types that were
-		 * not informed.
+		 * If there's no ':' separator in the entry, this is the default
+		 * value.  Otherwise it's a process type-specific entry.
 		 */
-		sep = strchr(tok, ':');
 		if (sep == NULL)
 		{
+			const struct config_enum_entry *entry;
 			bool		found = false;
 
 			/* Reject duplicates for generic log level. */
 			if (genericlevel != -1)
 			{
-				GUC_check_errdetail("Generic log level was already assigned.");
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Redundant specification of default log level.");
+				goto lmm_fail;
 			}
 
 			/* Is the log level valid? */
@@ -1358,9 +1352,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 		}
 		else
@@ -1368,23 +1360,19 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 			char	   *loglevel;
 			char	   *ptype;
 			bool		found = false;
+			int			level;
+			const struct config_enum_entry *entry;
 
-			ptype = guc_malloc(LOG, (sep - tok) + 1);
-			if (!ptype)
-			{
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
-			}
-			memcpy(ptype, tok, sep - tok);
-			ptype[sep - tok] = '\0';
+			ptype = tok;
 			loglevel = sep + 1;
+			*sep = '\0';
 
 			/* Is the log level valid? */
 			for (entry = server_message_level_options; entry && entry->name; entry++)
 			{
 				if (pg_strcasecmp(entry->name, loglevel) == 0)
 				{
+					level = entry->val;
 					found = true;
 					break;
 				}
@@ -1392,50 +1380,49 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 
 			if (!found)
 			{
-				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				GUC_check_errdetail("Unrecognized log level for process type \"%s\": \"%s\".",
+									ptype, loglevel);
+				goto lmm_fail;
 			}
 
-			/*
-			 * Is the process type name valid? There might be multiple entries
-			 * per process type, don't bail out because it can assign the
-			 * value for multiple entries.
-			 */
+			/* Is the process type name valid and unique? */
 			found = false;
 			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
 			{
-				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				if (pg_strcasecmp(process_types[i], ptype) == 0)
 				{
 					/* Reject duplicates for a process type. */
 					if (assigned[i])
 					{
-						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
-						guc_free(ptype);
-						guc_free(rawstring);
-						list_free(elemlist);
-						return false;
+						GUC_check_errdetail("Redundant log level specification for process type \"%s\".",
+											ptype);
+						goto lmm_fail;
 					}
 
-					newlevel[i] = entry->val;
+					newlevel[i] = level;
 					assigned[i] = true;
 					found = true;
+					break;
 				}
 			}
 
 			if (!found)
 			{
 				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
-				guc_free(ptype);
-				guc_free(rawstring);
-				list_free(elemlist);
-				return false;
+				goto lmm_fail;
 			}
 
-			guc_free(ptype);
+			/* Put the separator back in place */
+			*sep = ':';
 		}
+
+		/* all good */
+		continue;
+
+lmm_fail:
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
 	}
 
 	/*
@@ -1443,7 +1430,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	 */
 	if (genericlevel == -1)
 	{
-		GUC_check_errdetail("Generic log level was not defined.");
+		GUC_check_errdetail("Default log level was not defined.");
 		guc_free(rawstring);
 		list_free(elemlist);
 		return false;
@@ -1461,24 +1448,18 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	}
 
 	/*
-	 * 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.
+	 * To present a nice view to users, sort the output list by process type,
+	 * with the default value first.
 	 */
 	list_sort(elemlist, log_min_messages_cmp);
 
-	initStringInfo(&buf);
+	initStringInfoExt(&buf, strlen(rawstring) + 1);
 	foreach_ptr(char, tok, elemlist)
 	{
-		if (first)
-		{
+		if (foreach_current_index(tok) == 0)
 			appendStringInfoString(&buf, tok);
-			first = false;
-		}
 		else
-		{
 			appendStringInfo(&buf, ", %s", tok);
-		}
 	}
 
 	result = (char *) guc_malloc(LOG, buf.len + 1);
@@ -1505,6 +1486,10 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * list_sort() callback for check_log_min_messages.  The default element
+ * goes first; the rest are ordered by strcmp() of the process type.
+ */
 static int
 log_min_messages_cmp(const ListCell *a, const ListCell *b)
 {
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 08553c0f024..05b1feef3cf 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, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index efba20fa8b9..03f6c8479f2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0aebb9c8e24..53933f53b25 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -648,23 +648,15 @@ char	   *role_string;
 bool		in_hot_standby_guc;
 
 /*
- * log_min_messages
+ * set default log_min_messages to WARNING for all process types
  */
 int			log_min_messages[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = log_min_messages,
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
+	[bktype] = WARNING,
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
 };
 
-const char *const log_min_messages_process_types[] = {
-#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
-	[bktype] = bkcategory,
-#include "postmaster/proctypelist.h"
-#undef PG_PROCTYPE
-};
-
-
 /*
  * Displayable names for context types (enum GucContext)
  *
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0ddd805f0e7..4e259e84c2d 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -30,22 +30,22 @@
  */
 
 
-/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
-PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
-PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
-PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
-PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
-PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
-PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
-PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
-PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
-PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
-PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
-PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
-PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
-PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
-PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
+/* bktype, bkcategory, description, main_func, shmem_attach */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true)
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 9a6fd503009..616b10f80d6 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -955,19 +955,19 @@ SHOW log_min_messages;
 
 SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
 ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
-DETAIL:  Generic log level was not defined.
+DETAIL:  Default log level was not defined.
 SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
-DETAIL:  Generic log level was already assigned.
+DETAIL:  Redundant specification of default log level.
 SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
-DETAIL:  Process type "backend" was already assigned.
+DETAIL:  Redundant log level specification for process type "backend".
 SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
 DETAIL:  Unrecognized process type: "foo".
 SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
 ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
-DETAIL:  Unrecognized log level: "bar".
+DETAIL:  Unrecognized log level for process type "checkpointer": "bar".
 SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
 SHOW log_min_messages;
                                         log_min_messages                                         
-- 
2.47.3


--r3qaph7b72ke2fw4--





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

* [PATCH 3/5] Add CONCURRENTLY option to REPACK command.
@ 2026-03-11 14:16  Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 190+ messages in thread

From: Antonin Houska @ 2026-03-11 14:16 UTC (permalink / raw)

The REPACK command copies the relation data into a new file, creates new
indexes and eventually swaps the files. To make sure that the old file does
not change during the copying, the relation is locked in an exclusive mode,
which prevents applications from both reading and writing. (To keep the data
consistent, we'd only need to prevent the applications from writing, but even
reading needs to be blocked before we can swap the files - otherwise some
applications could continue using the old file. Currently, REPACK takes the
simple approach and acquires the exclusive lock in the beginning.

This patch introduces an alternative workflow, which only requires the
exclusive lock when the relation (and index) files are being swapped.
(Supposedly, the swapping should be pretty fast.) On the other hand, when we
copy the data to the new file, we allow applications to read from the relation
and even to write to it.

First, we scan the relation using a "historic snapshot", and insert all the
tuples satisfying this snapshot into the new relation.

Second, logical decoding is used to capture the data changes done by
applications during the copying (i.e. changes not yet committed from the
perspective of the historic snapshot mentioned above), and those are applied
to the new file before we acquire the exclusive lock that we need to swap the
files. (Of course, more data changes can take place while we are waiting for
the lock - these will be applied to the new file after we have acquired the
lock and before we swap the files.)

While the "concurrent data" changes are applied at specific stages (we cannot
do that until the intial copy is finished and indexes are built), a background
worker performs the decoding all the time. This way we minimize the amount of
not-yet-decoded WAL, so that archiving / recycling of WAL segments is not
delayed much. The decoded changes are written to files and passed to the
backed performing REPACK.

Since the logical decoding system, during its startup, waits until all the
transactions which already have XID assigned have finished, there is a risk of
deadlock if a transaction that already changed anything in the database tries
to acquire a conflicting lock on the table REPACK CONCURRENTLY is working
on. As an example, consider transaction running CREATE INDEX command on the
table that is being REPACKed CONCURRENTLY. On the other hand, DML commands
(INSERT, UPDATE, DELETE) are not a problem as their lock does not conflict
with REPACK CONCURRENTLY.

The current approach is that we accept the risk. If we tried to avoid it, it'd
be necessary to unlock the table before the logical decoding is setup and lock
it again afterwards. Such temporary unlocking would imply re-checking if the
table still meets all the requirements for REPACK CONCURRENTLY.

The WAL records produced by running DML commands on the new relation are
intentionally not fed to the logical decoding system. Doing so would introduce
significant overhead, and - as the new relation is never available for logical
replication - it would be useless.
---
 doc/src/sgml/monitoring.sgml                  |   37 +-
 doc/src/sgml/mvcc.sgml                        |   12 +-
 doc/src/sgml/ref/repack.sgml                  |  112 +-
 src/Makefile                                  |    1 +
 src/backend/access/heap/heapam.c              |   34 +-
 src/backend/access/heap/heapam_handler.c      |  208 +-
 src/backend/access/heap/rewriteheap.c         |    6 +-
 src/backend/catalog/system_views.sql          |   19 +-
 src/backend/commands/cluster.c                | 2295 ++++++++++++++++-
 src/backend/commands/matview.c                |    1 +
 src/backend/commands/tablecmds.c              |    1 +
 src/backend/commands/vacuum.c                 |   12 +-
 src/backend/libpq/pqmq.c                      |    5 +
 src/backend/meson.build                       |    1 +
 src/backend/postmaster/bgworker.c             |    5 +
 src/backend/replication/logical/decode.c      |   37 +-
 src/backend/replication/logical/logical.c     |    6 +-
 src/backend/replication/logical/snapbuild.c   |   11 +-
 .../replication/pgoutput_repack/Makefile      |   32 +
 .../replication/pgoutput_repack/meson.build   |   18 +
 .../pgoutput_repack/pgoutput_repack.c         |  202 ++
 src/backend/replication/walsender.c           |    2 +-
 src/backend/storage/ipc/procsignal.c          |    4 +
 .../storage/lmgr/generate-lwlocknames.pl      |    2 +-
 src/backend/tcop/postgres.c                   |    4 +
 .../utils/activity/wait_event_names.txt       |    1 +
 src/backend/utils/time/snapmgr.c              |    3 +-
 src/bin/psql/tab-complete.in.c                |    4 +-
 src/include/access/heapam.h                   |    5 +-
 src/include/access/heapam_xlog.h              |    2 +
 src/include/access/tableam.h                  |    5 +
 src/include/commands/cluster.h                |   55 +-
 src/include/commands/progress.h               |   17 +-
 src/include/replication/snapbuild.h           |    2 +-
 src/include/storage/lockdefs.h                |    4 +-
 src/include/storage/procsignal.h              |    1 +
 src/include/utils/snapmgr.h                   |    2 +
 src/test/modules/injection_points/Makefile    |    2 +
 .../injection_points/expected/repack.out      |  113 +
 .../expected/repack_toast.out                 |   64 +
 src/test/modules/injection_points/meson.build |    2 +
 .../injection_points/specs/repack.spec        |  142 +
 .../injection_points/specs/repack_toast.spec  |  105 +
 src/test/regress/expected/rules.out           |   19 +-
 src/tools/pgindent/typedefs.list              |    6 +
 45 files changed, 3364 insertions(+), 257 deletions(-)
 create mode 100644 src/backend/replication/pgoutput_repack/Makefile
 create mode 100644 src/backend/replication/pgoutput_repack/meson.build
 create mode 100644 src/backend/replication/pgoutput_repack/pgoutput_repack.c
 create mode 100644 src/test/modules/injection_points/expected/repack.out
 create mode 100644 src/test/modules/injection_points/expected/repack_toast.out
 create mode 100644 src/test/modules/injection_points/specs/repack.spec
 create mode 100644 src/test/modules/injection_points/specs/repack_toast.spec

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index cc014564c97..f52b5dc10e8 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -6839,14 +6839,35 @@ FROM pg_stat_get_backend_idset() AS backendid;
 
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
-       <structfield>heap_tuples_written</structfield> <type>bigint</type>
+       <structfield>heap_tuples_inserted</structfield> <type>bigint</type>
       </para>
       <para>
-       Number of heap tuples written.
+       Number of heap tuples inserted.
        This counter only advances when the phase is
        <literal>seq scanning heap</literal>,
-       <literal>index scanning heap</literal>
-       or <literal>writing new heap</literal>.
+       <literal>index scanning heap</literal>,
+       <literal>writing new heap</literal>
+       or <literal>catch-up</literal>.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>heap_tuples_updated</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of heap tuples updated.
+       This counter only advances when the phase is <literal>catch-up</literal>.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>heap_tuples_deleted</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of heap tuples deleted.
+       This counter only advances when the phase is <literal>catch-up</literal>.
       </para></entry>
      </row>
 
@@ -6927,6 +6948,14 @@ FROM pg_stat_get_backend_idset() AS backendid;
        <command>REPACK</command> is currently writing the new heap.
      </entry>
     </row>
+    <row>
+     <entry><literal>catch-up</literal></entry>
+     <entry>
+       <command>REPACK CONCURRENTLY</command> is currently processing the DML
+       commands that other transactions executed during any of the preceding
+       phases.
+     </entry>
+    </row>
     <row>
      <entry><literal>swapping relation files</literal></entry>
      <entry>
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index e775260936a..241caeb3593 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -1845,15 +1845,17 @@ SELECT pg_advisory_lock(q.id) FROM
    <title>Caveats</title>
 
    <para>
-    Some DDL commands, currently only <link linkend="sql-truncate"><command>TRUNCATE</command></link> and the
-    table-rewriting forms of <link linkend="sql-altertable"><command>ALTER TABLE</command></link>, are not
+    Some commands, currently only <link linkend="sql-truncate"><command>TRUNCATE</command></link>, the
+    table-rewriting forms of <link linkend="sql-altertable"><command>ALTER
+    TABLE</command></link> and <command>REPACK</command> with
+    the <literal>CONCURRENTLY</literal> option, are not
     MVCC-safe.  This means that after the truncation or rewrite commits, the
     table will appear empty to concurrent transactions, if they are using a
-    snapshot taken before the DDL command committed.  This will only be an
+    snapshot taken before the command committed.  This will only be an
     issue for a transaction that did not access the table in question
-    before the DDL command started &mdash; any transaction that has done so
+    before the command started &mdash; any transaction that has done so
     would hold at least an <literal>ACCESS SHARE</literal> table lock,
-    which would block the DDL command until that transaction completes.
+    which would block the truncating or rewriting command until that transaction completes.
     So these commands will not cause any apparent inconsistency in the
     table contents for successive queries on the target table, but they
     could cause visible inconsistency between the contents of the target
diff --git a/doc/src/sgml/ref/repack.sgml b/doc/src/sgml/ref/repack.sgml
index 8ccf7c7a417..170eb84bf43 100644
--- a/doc/src/sgml/ref/repack.sgml
+++ b/doc/src/sgml/ref/repack.sgml
@@ -28,6 +28,7 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING
 
     VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
     ANALYZE [ <replaceable class="parameter">boolean</replaceable> ]
+    CONCURRENTLY [ <replaceable class="parameter">boolean</replaceable> ]
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
 
@@ -54,7 +55,8 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING
    processes every table and materialized view in the current database that
    the current user has the <literal>MAINTAIN</literal> privilege on. This
    form of <command>REPACK</command> cannot be executed inside a transaction
-   block.
+   block.  Also, this form is not allowed if
+   the <literal>CONCURRENTLY</literal> option is used.
   </para>
 
   <para>
@@ -67,7 +69,8 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING
    When a table is being repacked, an <literal>ACCESS EXCLUSIVE</literal> lock
    is acquired on it. This prevents any other database operations (both reads
    and writes) from operating on the table until the <command>REPACK</command>
-   is finished.
+   is finished. If you want to keep the table accessible during the repacking,
+   consider using the <literal>CONCURRENTLY</literal> option.
   </para>
 
   <refsect2 id="sql-repack-notes-on-clustering" xreflabel="Notes on Clustering">
@@ -198,6 +201,111 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>CONCURRENTLY</literal></term>
+    <listitem>
+     <para>
+      Allow other transactions to use the table while it is being repacked.
+     </para>
+
+     <para>
+      Internally, <command>REPACK</command> copies the contents of the table
+      (ignoring dead tuples) into a new file, sorted by the specified index,
+      and also creates a new file for each index. Then it swaps the old and
+      new files for the table and all the indexes, and deletes the old
+      files. The <literal>ACCESS EXCLUSIVE</literal> lock is needed to make
+      sure that the old files do not change during the processing because the
+      changes would get lost due to the swap.
+     </para>
+
+     <para>
+      With the <literal>CONCURRENTLY</literal> option, the <literal>ACCESS
+      EXCLUSIVE</literal> lock is only acquired to swap the table and index
+      files. The data changes that took place during the creation of the new
+      table and index files are captured using logical decoding
+      (<xref linkend="logicaldecoding"/>) and applied before
+      the <literal>ACCESS EXCLUSIVE</literal> lock is requested. Thus the lock
+      is typically held only for the time needed to swap the files, which
+      should be pretty short. However, the time might still be noticeable if
+      too many data changes have been done to the table while
+      <command>REPACK</command> was waiting for the lock: those changes must
+      be processed just before the files are swapped, while the
+      <literal>ACCESS EXCLUSIVE</literal> lock is being held.
+     </para>
+
+     <para>
+      Note that <command>REPACK</command> with the
+      <literal>CONCURRENTLY</literal> option does not try to order the rows
+      inserted into the table after the repacking started. Also
+      note <command>REPACK</command> might fail to complete due to DDL
+      commands executed on the table by other transactions during the
+      repacking.
+     </para>
+
+     <note>
+      <para>
+       In addition to the temporary space requirements explained in
+       <xref linkend="sql-repack-notes-on-resources"/>,
+       the <literal>CONCURRENTLY</literal> option can add to the usage of
+       temporary space a bit more. The reason is that other transactions can
+       perform DML operations which cannot be applied to the new file until
+       <command>REPACK</command> has copied all the existing tuples from the
+       old file. Thus the tuples inserted into the old file during the copying
+       are also stored separately in a temporary file, until they can be
+       processed.
+      </para>
+     </note>
+
+     <para>
+      The <literal>CONCURRENTLY</literal> option cannot be used in the
+      following cases:
+
+      <itemizedlist>
+       <listitem>
+        <para>
+          The table is <literal>UNLOGGED</literal>.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+          The table is partitioned.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+          The table is a system catalog or a <acronym>TOAST</acronym> table.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         <command>REPACK</command> is executed inside a transaction block.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         The <link linkend="guc-max-replication-slots"><varname>max_replication_slots</varname></link>
+         configuration parameter does not allow for creation of an additional
+         replication slot.
+        </para>
+       </listitem>
+      </itemizedlist>
+     </para>
+
+     <warning>
+      <para>
+       <command>REPACK</command> with the <literal>CONCURRENTLY</literal>
+       option is not MVCC-safe, see <xref linkend="mvcc-caveats"/> for
+       details.
+      </para>
+     </warning>
+
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>VERBOSE</literal></term>
     <listitem>
diff --git a/src/Makefile b/src/Makefile
index 2f31a2f20a7..b18c9a14ffa 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -23,6 +23,7 @@ SUBDIRS = \
 	interfaces \
 	backend/replication/libpqwalreceiver \
 	backend/replication/pgoutput \
+	backend/replication/pgoutput_repack \
 	fe_utils \
 	bin \
 	pl \
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 8f1c11a9350..f25fac19a97 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -61,7 +61,8 @@ static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 								  Buffer newbuf, HeapTuple oldtup,
 								  HeapTuple newtup, HeapTuple old_key_tuple,
-								  bool all_visible_cleared, bool new_all_visible_cleared);
+								  bool all_visible_cleared, bool new_all_visible_cleared,
+								  bool walLogical);
 #ifdef USE_ASSERT_CHECKING
 static void check_lock_if_inplace_updateable_rel(Relation relation,
 												 const ItemPointerData *otid,
@@ -2843,7 +2844,7 @@ xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
 TM_Result
 heap_delete(Relation relation, const ItemPointerData *tid,
 			CommandId cid, Snapshot crosscheck, bool wait,
-			TM_FailureData *tmfd, bool changingPart)
+			TM_FailureData *tmfd, bool changingPart, bool walLogical)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
@@ -3090,7 +3091,8 @@ l1:
 	 * Compute replica identity tuple before entering the critical section so
 	 * we don't PANIC upon a memory allocation failure.
 	 */
-	old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied);
+	old_key_tuple = walLogical ?
+		ExtractReplicaIdentity(relation, &tp, true, &old_key_copied) : NULL;
 
 	/*
 	 * If this is the first possibly-multixact-able operation in the current
@@ -3180,6 +3182,15 @@ l1:
 				xlrec.flags |= XLH_DELETE_CONTAINS_OLD_KEY;
 		}
 
+		/*
+		 * Unlike UPDATE, DELETE is decoded even if there is no old key, so it
+		 * does not help to clear both XLH_DELETE_CONTAINS_OLD_TUPLE and
+		 * XLH_DELETE_CONTAINS_OLD_KEY. Thus we need an extra flag. TODO
+		 * Consider not decoding tuples w/o the old tuple/key instead.
+		 */
+		if (!walLogical)
+			xlrec.flags |= XLH_DELETE_NO_LOGICAL;
+
 		XLogBeginInsert();
 		XLogRegisterData(&xlrec, SizeOfHeapDelete);
 
@@ -3272,7 +3283,8 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid)
 	result = heap_delete(relation, tid,
 						 GetCurrentCommandId(true), InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &tmfd, false /* changingPart */ );
+						 &tmfd, false,	/* changingPart */
+						 true /* walLogical */ );
 	switch (result)
 	{
 		case TM_SelfModified:
@@ -3313,7 +3325,7 @@ TM_Result
 heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			TM_FailureData *tmfd, LockTupleMode *lockmode,
-			TU_UpdateIndexes *update_indexes)
+			TU_UpdateIndexes *update_indexes, bool walLogical)
 {
 	TM_Result	result;
 	TransactionId xid = GetCurrentTransactionId();
@@ -4206,7 +4218,8 @@ l2:
 								 newbuf, &oldtup, heaptup,
 								 old_key_tuple,
 								 all_visible_cleared,
-								 all_visible_cleared_new);
+								 all_visible_cleared_new,
+								 walLogical);
 		if (newbuf != buffer)
 		{
 			PageSetLSN(BufferGetPage(newbuf), recptr);
@@ -4564,7 +4577,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup
 	result = heap_update(relation, otid, tup,
 						 GetCurrentCommandId(true), InvalidSnapshot,
 						 true /* wait for commit */ ,
-						 &tmfd, &lockmode, update_indexes);
+						 &tmfd, &lockmode, update_indexes,
+						 true /* walLogical */ );
 	switch (result)
 	{
 		case TM_SelfModified:
@@ -8925,7 +8939,8 @@ static XLogRecPtr
 log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup, HeapTuple newtup,
 				HeapTuple old_key_tuple,
-				bool all_visible_cleared, bool new_all_visible_cleared)
+				bool all_visible_cleared, bool new_all_visible_cleared,
+				bool walLogical)
 {
 	xl_heap_update xlrec;
 	xl_heap_header xlhdr;
@@ -8936,7 +8951,8 @@ log_heap_update(Relation reln, Buffer oldbuf,
 				suffixlen = 0;
 	XLogRecPtr	recptr;
 	Page		page = BufferGetPage(newbuf);
-	bool		need_tuple_data = RelationIsLogicallyLogged(reln);
+	bool		need_tuple_data = RelationIsLogicallyLogged(reln) &&
+		walLogical;
 	bool		init;
 	int			bufflags;
 
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 5137d2510ea..f4c62e5bc7a 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -309,7 +309,8 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
 	 * the storage itself is cleaning the dead tuples by itself, it is the
 	 * time to call the index tuple deletion also.
 	 */
-	return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart);
+	return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart,
+					   true);
 }
 
 
@@ -328,7 +329,7 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
 	tuple->t_tableOid = slot->tts_tableOid;
 
 	result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
-						 tmfd, lockmode, update_indexes);
+						 tmfd, lockmode, update_indexes, true);
 	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/*
@@ -685,13 +686,14 @@ static void
 heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 								 Relation OldIndex, bool use_sort,
 								 TransactionId OldestXmin,
+								 Snapshot snapshot,
 								 TransactionId *xid_cutoff,
 								 MultiXactId *multi_cutoff,
 								 double *num_tuples,
 								 double *tups_vacuumed,
 								 double *tups_recently_dead)
 {
-	RewriteState rwstate;
+	RewriteState rwstate = NULL;
 	IndexScanDesc indexScan;
 	TableScanDesc tableScan;
 	HeapScanDesc heapScan;
@@ -705,6 +707,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	bool	   *isnull;
 	BufferHeapTupleTableSlot *hslot;
 	BlockNumber prev_cblock = InvalidBlockNumber;
+	bool		concurrent = snapshot != NULL;
 
 	/* Remember if it's a system catalog */
 	is_system_catalog = IsSystemRelation(OldHeap);
@@ -720,9 +723,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	values = palloc_array(Datum, natts);
 	isnull = palloc_array(bool, natts);
 
-	/* Initialize the rewrite operation */
-	rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff,
-								 *multi_cutoff);
+	/*
+	 * Initialize the rewrite operation.
+	 */
+	if (!concurrent)
+		rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
+									 *xid_cutoff, *multi_cutoff);
 
 
 	/* Set up sorting if wanted */
@@ -737,6 +743,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	 * Prepare to scan the OldHeap.  To ensure we see recently-dead tuples
 	 * that still need to be copied, we scan with SnapshotAny and use
 	 * HeapTupleSatisfiesVacuum for the visibility test.
+	 *
+	 * In the CONCURRENTLY case, we do regular MVCC visibility tests, using
+	 * the snapshot passed by the caller.
 	 */
 	if (OldIndex != NULL && !use_sort)
 	{
@@ -753,7 +762,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 
 		tableScan = NULL;
 		heapScan = NULL;
-		indexScan = index_beginscan(OldHeap, OldIndex, SnapshotAny, NULL, 0, 0);
+		indexScan = index_beginscan(OldHeap, OldIndex,
+									snapshot ? snapshot : SnapshotAny,
+									NULL, 0, 0);
 		index_rescan(indexScan, NULL, 0, NULL, 0);
 	}
 	else
@@ -762,7 +773,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 		pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 									 PROGRESS_REPACK_PHASE_SEQ_SCAN_HEAP);
 
-		tableScan = table_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL);
+		tableScan = table_beginscan(OldHeap,
+									snapshot ? snapshot : SnapshotAny,
+									0, (ScanKey) NULL);
 		heapScan = (HeapScanDesc) tableScan;
 		indexScan = NULL;
 
@@ -838,83 +851,91 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 		buf = hslot->buffer;
 
 		/*
-		 * To be able to guarantee that we can set the hint bit, acquire an
-		 * exclusive lock on the old buffer. We need the hint bits, set in
-		 * heapam_relation_copy_for_cluster() -> HeapTupleSatisfiesVacuum(),
-		 * to be set, as otherwise reform_and_rewrite_tuple() ->
-		 * rewrite_heap_tuple() will get confused. Specifically,
-		 * rewrite_heap_tuple() checks for HEAP_XMAX_INVALID in the old tuple
-		 * to determine whether to check the old-to-new mapping hash table.
-		 *
-		 * It'd be better if we somehow could avoid setting hint bits on the
-		 * old page. One reason to use VACUUM FULL are very bloated tables -
-		 * rewriting most of the old table during VACUUM FULL doesn't exactly
-		 * help...
+		 * Regarding CONCURRENTLY, see the comments on MVCC snapshot above.
 		 */
-		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-
-		switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf))
+		if (!concurrent)
 		{
-			case HEAPTUPLE_DEAD:
-				/* Definitely dead */
-				isdead = true;
-				break;
-			case HEAPTUPLE_RECENTLY_DEAD:
-				*tups_recently_dead += 1;
-				pg_fallthrough;
-			case HEAPTUPLE_LIVE:
-				/* Live or recently dead, must copy it */
-				isdead = false;
-				break;
-			case HEAPTUPLE_INSERT_IN_PROGRESS:
+			/*
+			 * To be able to guarantee that we can set the hint bit, acquire
+			 * an exclusive lock on the old buffer. We need the hint bits, set
+			 * in heapam_relation_copy_for_cluster() ->
+			 * HeapTupleSatisfiesVacuum(), to be set, as otherwise
+			 * reform_and_rewrite_tuple() -> rewrite_heap_tuple() will get
+			 * confused. Specifically, rewrite_heap_tuple() checks for
+			 * HEAP_XMAX_INVALID in the old tuple to determine whether to
+			 * check the old-to-new mapping hash table.
+			 *
+			 * It'd be better if we somehow could avoid setting hint bits on
+			 * the old page. One reason to use VACUUM FULL are very bloated
+			 * tables - rewriting most of the old table during VACUUM FULL
+			 * doesn't exactly help...
+			 */
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
-				/*
-				 * Since we hold exclusive lock on the relation, normally the
-				 * only way to see this is if it was inserted earlier in our
-				 * own transaction.  However, it can happen in system
-				 * catalogs, since we tend to release write lock before commit
-				 * there.  Give a warning if neither case applies; but in any
-				 * case we had better copy it.
-				 */
-				if (!is_system_catalog &&
-					!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))
-					elog(WARNING, "concurrent insert in progress within table \"%s\"",
-						 RelationGetRelationName(OldHeap));
-				/* treat as live */
-				isdead = false;
-				break;
-			case HEAPTUPLE_DELETE_IN_PROGRESS:
+			switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf))
+			{
+				case HEAPTUPLE_DEAD:
+					/* Definitely dead */
+					isdead = true;
+					break;
+				case HEAPTUPLE_RECENTLY_DEAD:
+					*tups_recently_dead += 1;
+					pg_fallthrough;
+				case HEAPTUPLE_LIVE:
+					/* Live or recently dead, must copy it */
+					isdead = false;
+					break;
+				case HEAPTUPLE_INSERT_IN_PROGRESS:
 
-				/*
-				 * Similar situation to INSERT_IN_PROGRESS case.
-				 */
-				if (!is_system_catalog &&
-					!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data)))
-					elog(WARNING, "concurrent delete in progress within table \"%s\"",
-						 RelationGetRelationName(OldHeap));
-				/* treat as recently dead */
-				*tups_recently_dead += 1;
-				isdead = false;
-				break;
-			default:
-				elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
-				isdead = false; /* keep compiler quiet */
-				break;
-		}
+					/*
+					 * As long as we hold exclusive lock on the relation,
+					 * normally the only way to see this is if it was inserted
+					 * earlier in our own transaction.  However, it can happen
+					 * in system catalogs, since we tend to release write lock
+					 * before commit there. Give a warning if neither case
+					 * applies; but in any case we had better copy it.
+					 */
+					if (!is_system_catalog &&
+						!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))
+						elog(WARNING, "concurrent insert in progress within table \"%s\"",
+							 RelationGetRelationName(OldHeap));
+					/* treat as live */
+					isdead = false;
+					break;
+				case HEAPTUPLE_DELETE_IN_PROGRESS:
 
-		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+					/*
+					 * Similar situation to INSERT_IN_PROGRESS case.
+					 */
+					if (!is_system_catalog &&
+						!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data)))
+						elog(WARNING, "concurrent delete in progress within table \"%s\"",
+							 RelationGetRelationName(OldHeap));
+					/* treat as recently dead */
+					*tups_recently_dead += 1;
+					isdead = false;
+					break;
+				default:
+					elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
+					isdead = false; /* keep compiler quiet */
+					break;
+			}
 
-		if (isdead)
-		{
-			*tups_vacuumed += 1;
-			/* heap rewrite module still needs to see it... */
-			if (rewrite_heap_dead_tuple(rwstate, tuple))
+			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+			if (isdead)
 			{
-				/* A previous recently-dead tuple is now known dead */
 				*tups_vacuumed += 1;
-				*tups_recently_dead -= 1;
+				/* heap rewrite module still needs to see it... */
+				if (rewrite_heap_dead_tuple(rwstate, tuple))
+				{
+					/* A previous recently-dead tuple is now known dead */
+					*tups_vacuumed += 1;
+					*tups_recently_dead -= 1;
+				}
+
+				continue;
 			}
-			continue;
 		}
 
 		*num_tuples += 1;
@@ -933,7 +954,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 		{
 			const int	ct_index[] = {
 				PROGRESS_REPACK_HEAP_TUPLES_SCANNED,
-				PROGRESS_REPACK_HEAP_TUPLES_WRITTEN
+				PROGRESS_REPACK_HEAP_TUPLES_INSERTED
 			};
 			int64		ct_val[2];
 
@@ -991,7 +1012,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 									 values, isnull,
 									 rwstate);
 			/* Report n_tuples */
-			pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_WRITTEN,
+			pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
 										 n_tuples);
 		}
 
@@ -999,7 +1020,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	}
 
 	/* Write out any remaining tuples, and fsync if needed */
-	end_heap_rewrite(rwstate);
+	if (rwstate)
+		end_heap_rewrite(rwstate);
 
 	/* Clean up */
 	pfree(values);
@@ -2392,6 +2414,10 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
  * SET WITHOUT OIDS.
  *
  * So, we must reconstruct the tuple from component Datums.
+ *
+ * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
+ * case we still need to deform/form the tuple. TODO Shouldn't we rename the
+ * function, as might not do any rewrite?
  */
 static void
 reform_and_rewrite_tuple(HeapTuple tuple,
@@ -2414,8 +2440,28 @@ reform_and_rewrite_tuple(HeapTuple tuple,
 
 	copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
 
-	/* The heap rewrite module does the rest */
-	rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+	if (rwstate)
+		/* The heap rewrite module does the rest */
+		rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+	else
+	{
+		/*
+		 * Insert tuple when processing REPACK CONCURRENTLY.
+		 *
+		 * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+		 * difficult to do the same in the catch-up phase (as the logical
+		 * decoding does not provide us with sufficient visibility
+		 * information). Thus we must use heap_insert() both during the
+		 * catch-up and here.
+		 *
+		 * The following is like simple_heap_insert() except that we pass the
+		 * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
+		 * the relation files, it drops this relation, so no logical
+		 * replication subscription should need the data.
+		 */
+		heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
+					HEAP_INSERT_NO_LOGICAL, NULL);
+	}
 
 	heap_freetuple(copiedTuple);
 }
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 6b19ac3030d..d706856e7a5 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -621,9 +621,9 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
 		int			options = HEAP_INSERT_SKIP_FSM;
 
 		/*
-		 * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data
-		 * for the TOAST table are not logically decoded.  The main heap is
-		 * WAL-logged as XLOG FPI records, which are not logically decoded.
+		 * While rewriting the heap for REPACK, make sure data for the TOAST
+		 * table are not logically decoded.  The main heap is WAL-logged as
+		 * XLOG FPI records, which are not logically decoded.
 		 */
 		options |= HEAP_INSERT_NO_LOGICAL;
 
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 339c016e510..d7a6e306db9 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1326,16 +1326,19 @@ CREATE VIEW pg_stat_progress_repack AS
                       WHEN 2 THEN 'index scanning heap'
                       WHEN 3 THEN 'sorting tuples'
                       WHEN 4 THEN 'writing new heap'
-                      WHEN 5 THEN 'swapping relation files'
-                      WHEN 6 THEN 'rebuilding index'
-                      WHEN 7 THEN 'performing final cleanup'
+                      WHEN 5 THEN 'catch-up'
+                      WHEN 6 THEN 'swapping relation files'
+                      WHEN 7 THEN 'rebuilding index'
+                      WHEN 8 THEN 'performing final cleanup'
                       END AS phase,
         CAST(S.param3 AS oid) AS repack_index_relid,
         S.param4 AS heap_tuples_scanned,
-        S.param5 AS heap_tuples_written,
-        S.param6 AS heap_blks_total,
-        S.param7 AS heap_blks_scanned,
-        S.param8 AS index_rebuild_count
+        S.param5 AS heap_tuples_inserted,
+        S.param6 AS heap_tuples_updated,
+        S.param7 AS heap_tuples_deleted,
+        S.param8 AS heap_blks_total,
+        S.param9 AS heap_blks_scanned,
+        S.param10 AS index_rebuild_count
     FROM pg_stat_get_progress_info('REPACK') AS S
         LEFT JOIN pg_database D ON S.datid = D.oid;
 
@@ -1353,7 +1356,7 @@ CREATE VIEW pg_stat_progress_cluster AS
         phase,
         repack_index_relid AS cluster_index_relid,
         heap_tuples_scanned,
-        heap_tuples_written,
+        heap_tuples_inserted + heap_tuples_updated AS heap_tuples_written,
         heap_blks_total,
         heap_blks_scanned,
         index_rebuild_count
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 3bfaa663699..fe419ef808e 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -4,6 +4,22 @@
  *    REPACK a table; formerly known as CLUSTER.  VACUUM FULL also uses
  *    parts of this code.
  *
+ * There are two somewhat different ways to rewrite a table.  In non-
+ * concurrent mode, it's easy: take AccessExclusiveLock, create a new
+ * transient relation, copy the tuples over to the relfilenode of the new
+ * relation, swap the relfilenodes, then drop the old relation.
+ *
+ * In concurrent mode, we lock the table with only ShareUpdateExclusiveLock,
+ * then do an initial copy as above.  However, while the tuples are being
+ * copied, concurrent transactions could modify the table. To cope with those
+ * changes, we rely on logical decoding to obtain them from WAL.  A bgworker
+ * consumes WAL while the initial copy is ongoing (to prevent excessive WAL
+ * from being reserved), and accumulates the changes in a file.  Once the
+ * initial copy is complete, we read the changes from the file and re-apply
+ * them on the new heap.  Then we upgrade our ShareUpdateExclusiveLock to
+ * AccessExclusiveLock and swap the relfilenodes.  This way, the time we hold
+ * a strong lock on the table is much reduced, and the bloat is eliminated.
+ *
  *
  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994-5, Regents of the University of California
@@ -24,6 +40,11 @@
 #include "access/toast_internals.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xlog_internal.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "access/xlogwait.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
@@ -31,6 +52,7 @@
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_control.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
@@ -38,15 +60,26 @@
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
+#include "executor/executor.h"
+#include "libpq/pqformat.h"
+#include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
+#include "replication/decode.h"
+#include "replication/logical.h"
+#include "replication/snapbuild.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
+#include "storage/proc.h"
+#include "storage/procsignal.h"
+#include "tcop/tcopprot.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
+#include "utils/injection_point.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -54,6 +87,7 @@
 #include "utils/relmapper.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/wait_event_types.h"
 
 /*
  * This struct is used to pass around the information on tables to be
@@ -66,12 +100,171 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+/*
+ * The following definitions are used for concurrent processing.
+ */
+
+/*
+ * The locators are used to avoid logical decoding of data that we do not need
+ * for our table.
+ */
+static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
+static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
+
+/*
+ * Everything we need to call ExecInsertIndexTuples().
+ */
+typedef struct IndexInsertState
+{
+	ResultRelInfo *rri;
+	EState	   *estate;
+} IndexInsertState;
+
+/* The WAL segment being decoded. */
+static XLogSegNo repack_current_segment = 0;
+
+/*
+ * The first file exported by the decoding worker must contain a snapshot, the
+ * following ones contain the data changes.
+ */
+#define WORKER_FILE_SNAPSHOT	0
+
+/*
+ * Information needed to apply concurrent data changes.
+ */
+typedef struct ChangeDest
+{
+	/* The relation the changes are applied to. */
+	Relation	rel;
+
+	/*
+	 * The following is needed to find the existing tuple if the change is
+	 * UPDATE or DELETE. 'ident_key' should have all the fields except for
+	 * 'sk_argument' initialized.
+	 */
+	Relation	ident_index;
+	ScanKey		ident_key;
+	int			ident_key_nentries;
+
+	/* Needed to update indexes of rel_dst. */
+	IndexInsertState *iistate;
+
+	/*
+	 * Sequential number of the file containing the changes.
+	 *
+	 * TODO This field makes the structure name less descriptive. Should we
+	 * rename it, e.g. to ChangeApplyInfo?
+	 */
+	int			file_seq;
+} ChangeDest;
+
+/*
+ * Layout of shared memory used for communication between backend and the
+ * worker that performs logical decoding of data changes
+ */
+typedef struct DecodingWorkerShared
+{
+	/* Is the decoding initialized? */
+	bool		initialized;
+
+	/*
+	 * Once the worker has reached this LSN, it should close the current
+	 * output file and either create a new one or exit, according to the field
+	 * 'done'. If the value is InvalidXLogRecPtr, the worker should decode all
+	 * the WAL available and keep checking this field. It is ok if the worker
+	 * had already decoded records whose LSN is >= lsn_upto before this field
+	 * has been set.
+	 */
+	XLogRecPtr	lsn_upto;
+
+	/* Exit after closing the current file? */
+	bool		done;
+
+	/* The output is stored here. */
+	SharedFileSet sfs;
+
+	/* Number of the last file exported by the worker. */
+	int			last_exported;
+
+	/* Synchronize access to the fields above. */
+	slock_t		mutex;
+
+	/* Database to connect to. */
+	Oid			dbid;
+
+	/* Role to connect as. */
+	Oid			roleid;
+
+	/* Decode data changes of this relation. */
+	Oid			relid;
+
+	/* The backend uses this to wait for the worker. */
+	ConditionVariable cv;
+
+	/* Info to signal the backend. */
+	PGPROC	   *backend_proc;
+	pid_t		backend_pid;
+	ProcNumber	backend_proc_number;
+
+	/*
+	 * Memory the queue is located in.
+	 *
+	 * For considerations on the value see the comments of
+	 * PARALLEL_ERROR_QUEUE_SIZE.
+	 */
+#define REPACK_ERROR_QUEUE_SIZE			16384
+	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
+} DecodingWorkerShared;
+
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static inline void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
+/*
+ * Backend-local information to control the decoding worker.
+ */
+typedef struct DecodingWorker
+{
+	/* The worker. */
+	BackgroundWorkerHandle *handle;
+
+	/* DecodingWorkerShared is in this segment. */
+	dsm_segment *seg;
+
+	/* Handle of the error queue. */
+	shm_mq_handle *error_mqh;
+} DecodingWorker;
+
+/* Pointer to currently running decoding worker. */
+static DecodingWorker *decoding_worker = NULL;
+
+/*
+ * Is there a message sent by a repack worker that the backend needs to
+ * receive?
+ */
+volatile sig_atomic_t RepackMessagePending = false;
+
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
-								Oid indexOid, Oid userid, int options);
-static void rebuild_relation(Relation OldHeap, Relation index, bool verbose);
+								Oid indexOid, Oid userid, LOCKMODE lmode,
+								int options);
+static void check_repack_concurrently_requirements(Relation rel,
+												   Oid *ident_idx_p);
+static void rebuild_relation(Relation OldHeap, Relation index, bool verbose,
+							 Oid ident_idx);
 static void copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
-							bool verbose, bool *pSwapToastByContent,
-							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
+							Snapshot snapshot,
+							bool verbose,
+							bool *pSwapToastByContent,
+							TransactionId *pFreezeXid,
+							MultiXactId *pCutoffMulti);
 static List *get_tables_to_repack(RepackCommand cmd, bool usingindex,
 								  MemoryContext permcxt);
 static List *get_tables_to_repack_partitioned(RepackCommand cmd,
@@ -79,13 +272,55 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+
+static LogicalDecodingContext *setup_logical_decoding(Oid relid);
+static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
+									  DecodingWorkerShared *shared);
+static void apply_concurrent_changes(BufFile *file, ChangeDest *dest);
+static void apply_concurrent_insert(Relation rel, HeapTuple tup,
+									IndexInsertState *iistate,
+									TupleTableSlot *index_slot);
+static void apply_concurrent_update(Relation rel, HeapTuple tup,
+									HeapTuple tup_target,
+									IndexInsertState *iistate,
+									TupleTableSlot *index_slot);
+static void apply_concurrent_delete(Relation rel, HeapTuple tup_target);
+static HeapTuple find_target_tuple(Relation rel, ChangeDest *dest,
+								   HeapTuple tup_key,
+								   TupleTableSlot *ident_slot);
+static void process_concurrent_changes(XLogRecPtr end_of_wal,
+									   ChangeDest *dest,
+									   bool done);
+static IndexInsertState *get_index_insert_state(Relation relation,
+												Oid ident_index_id,
+												Relation *ident_index_p);
+static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src,
+								  int *nentries);
+static void free_index_insert_state(IndexInsertState *iistate);
+static void cleanup_logical_decoding(LogicalDecodingContext *ctx);
+static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
+											   Oid identIdx,
+											   TransactionId frozenXid,
+											   MultiXactId cutoffMulti);
+static List *build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes);
 static Relation process_single_relation(RepackStmt *stmt,
+										LOCKMODE lockmode,
+										bool isTopLevel,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
+static void start_decoding_worker(Oid relid);
+static void stop_decoding_worker(void);
+static void repack_worker_internal(dsm_segment *seg);
+static void export_initial_snapshot(Snapshot snapshot,
+									DecodingWorkerShared *shared);
+static Snapshot get_initial_snapshot(DecodingWorker *worker);
+static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+#define REPL_PLUGIN_NAME   "pgoutput_repack"
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -115,6 +350,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	ClusterParams params = {0};
 	Relation	rel = NULL;
 	MemoryContext repack_context;
+	LOCKMODE	lockmode;
 	List	   *rtcs;
 
 	/* Parse option list */
@@ -125,6 +361,16 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 		else if (strcmp(opt->defname, "analyze") == 0 ||
 				 strcmp(opt->defname, "analyse") == 0)
 			params.options |= defGetBoolean(opt) ? CLUOPT_ANALYZE : 0;
+		else if (strcmp(opt->defname, "concurrently") == 0 &&
+				 defGetBoolean(opt))
+		{
+			if (stmt->command != REPACK_COMMAND_REPACK)
+				ereport(ERROR,
+						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						errmsg("CONCURRENTLY option not supported for %s",
+							   RepackCommandAsString(stmt->command)));
+			params.options |= CLUOPT_CONCURRENT;
+		}
 		else
 			ereport(ERROR,
 					errcode(ERRCODE_SYNTAX_ERROR),
@@ -134,13 +380,25 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
+	/*
+	 * Determine the lock mode expected by cluster_rel().
+	 *
+	 * In the exclusive case, we obtain AccessExclusiveLock right away to
+	 * avoid lock-upgrade hazard in the single-transaction case. In the
+	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
+	 * of processing, supposedly for very short time. Until then, we'll have
+	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
+	 */
+	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
+		AccessExclusiveLock : ShareUpdateExclusiveLock;
+
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
 	 * the relation is a partitioned table, in which case we fall through.
 	 */
 	if (stmt->relation != NULL)
 	{
-		rel = process_single_relation(stmt, &params);
+		rel = process_single_relation(stmt, lockmode, isTopLevel, &params);
 		if (rel == NULL)
 			return;				/* all done */
 	}
@@ -156,10 +414,29 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					   "REPACK (ANALYZE)"));
 
 	/*
-	 * By here, we know we are in a multi-table situation.  In order to avoid
-	 * holding locks for too long, we want to process each table in its own
-	 * transaction.  This forces us to disallow running inside a user
-	 * transaction block.
+	 * By here, we know we are in a multi-table situation.
+	 *
+	 * Concurrent processing is currently considered rather special (e.g. in
+	 * terms of resources consumed) so it is not performed in bulk.
+	 */
+	if (params.options & CLUOPT_CONCURRENT)
+	{
+		if (rel != NULL)
+		{
+			Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+			ereport(ERROR,
+					errmsg("REPACK CONCURRENTLY not supported for partitioned tables"),
+					errhint("Consider running the command for individual partitions."));
+		}
+		else
+			ereport(ERROR,
+					errmsg("REPACK CONCURRENTLY requires explicit table name"));
+	}
+
+	/*
+	 * In order to avoid holding locks for too long, we want to process each
+	 * table in its own transaction.  This forces us to disallow running
+	 * inside a user transaction block.
 	 */
 	PreventInTransactionBlock(isTopLevel, RepackCommandAsString(stmt->command));
 
@@ -253,7 +530,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 		 * Open the target table, coping with the case where it has been
 		 * dropped.
 		 */
-		rel = try_table_open(rtc->tableOid, AccessExclusiveLock);
+		rel = try_table_open(rtc->tableOid, lockmode);
 		if (rel == NULL)
 		{
 			CommitTransactionCommand();
@@ -264,7 +541,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 		PushActiveSnapshot(GetTransactionSnapshot());
 
 		/* Process this table */
-		cluster_rel(stmt->command, rel, rtc->indexOid, &params);
+		cluster_rel(stmt->command, rel, rtc->indexOid, &params, isTopLevel);
 		/* cluster_rel closes the relation, but keeps lock */
 
 		PopActiveSnapshot();
@@ -293,22 +570,54 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
  * If indexOid is InvalidOid, the table will be rewritten in physical order
  * instead of index order.
  *
+ * Note that, in the concurrent case, the function releases the lock at some
+ * point, in order to get AccessExclusiveLock for the final steps (i.e. to
+ * swap the relation files). To make things simpler, the caller should expect
+ * OldHeap to be closed on return, regardless CLUOPT_CONCURRENT. (The
+ * AccessExclusiveLock is kept till the end of the transaction.)
+ *
  * 'cmd' indicates which command is being executed, to be used for error
  * messages.
  */
 void
 cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
-			ClusterParams *params)
+			ClusterParams *params, bool isTopLevel)
 {
 	Oid			tableOid = RelationGetRelid(OldHeap);
+	Relation	index;
+	LOCKMODE	lmode;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
-	Relation	index;
+	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
+	Oid			ident_idx = InvalidOid;
+
+	/*
+	 * The lock mode is AccessExclusiveLock for normal processing and
+	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
+	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
+	 * called concurrently for the same relation).
+	 */
+	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+
+	/* There are specific requirements on concurrent processing. */
+	if (concurrent)
+	{
+		/*
+		 * Make sure we have no XID assigned, otherwise call of
+		 * setup_logical_decoding() can cause a deadlock.
+		 *
+		 * The existence of transaction block actually does not imply that XID
+		 * was already assigned, but it very likely is. We might want to check
+		 * the result of GetCurrentTransactionIdIfAny() instead, but that
+		 * would be less clear from user's perspective.
+		 */
+		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
-	Assert(CheckRelationLockedByMe(OldHeap, AccessExclusiveLock, false));
+		check_repack_concurrently_requirements(OldHeap, &ident_idx);
+	}
 
 	/* Check for user-requested abort. */
 	CHECK_FOR_INTERRUPTS();
@@ -334,10 +643,13 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * If this is a single-transaction CLUSTER, we can skip these tests. We
 	 * *must* skip the one on indisclustered since it would reject an attempt
 	 * to cluster a not-previously-clustered index.
+	 *
+	 * XXX move [some of] these comments to where the RECHECK flag is
+	 * determined?
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
-							 params->options))
+							 lmode, params->options))
 		goto out;
 
 	/*
@@ -353,6 +665,12 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 				errmsg("cannot execute %s on a shared catalog",
 					   RepackCommandAsString(cmd)));
 
+	/*
+	 * The CONCURRENTLY case should have been rejected earlier because it does
+	 * not support system catalogs.
+	 */
+	Assert(!(OldHeap->rd_rel->relisshared && concurrent));
+
 	/*
 	 * Don't process temp tables of other backends ... their local buffer
 	 * manager is not going to cope.
@@ -374,7 +692,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	if (OidIsValid(indexOid))
 	{
 		/* verify the index is good and lock it */
-		check_index_is_clusterable(OldHeap, indexOid, AccessExclusiveLock);
+		check_index_is_clusterable(OldHeap, indexOid, lmode);
 		/* also open it */
 		index = index_open(indexOid, NoLock);
 	}
@@ -409,7 +727,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW &&
 		!RelationIsPopulated(OldHeap))
 	{
-		relation_close(OldHeap, AccessExclusiveLock);
+		if (index)
+			index_close(index, lmode);
+		relation_close(OldHeap, lmode);
 		goto out;
 	}
 
@@ -422,11 +742,34 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * invalid, because we move tuples around.  Promote them to relation
 	 * locks.  Predicate locks on indexes will be promoted when they are
 	 * reindexed.
+	 *
+	 * During concurrent processing, the heap as well as its indexes stay in
+	 * operation, so we postpone this step until they are locked using
+	 * AccessExclusiveLock near the end of the processing.
 	 */
-	TransferPredicateLocksToHeapRelation(OldHeap);
+	if (!concurrent)
+		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	rebuild_relation(OldHeap, index, verbose);
+	PG_TRY();
+	{
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
+	}
+	PG_FINALLY();
+	{
+		if (concurrent)
+		{
+			/*
+			 * Since during normal operation the worker was already asked to
+			 * exit, stopping it explicitly is especially important on ERROR.
+			 * However it still seems a good practice to make sure that the
+			 * worker never survives the REPACK command.
+			 */
+			stop_decoding_worker();
+		}
+	}
+	PG_END_TRY();
+
 	/* rebuild_relation closes OldHeap, and index if valid */
 
 out:
@@ -445,14 +788,14 @@ out:
  */
 static bool
 cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
-					Oid userid, int options)
+					Oid userid, LOCKMODE lmode, int options)
 {
 	Oid			tableOid = RelationGetRelid(OldHeap);
 
 	/* Check that the user still has privileges for the relation */
 	if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
 	{
-		relation_close(OldHeap, AccessExclusiveLock);
+		relation_close(OldHeap, lmode);
 		return false;
 	}
 
@@ -466,7 +809,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 */
 	if (RELATION_IS_OTHER_TEMP(OldHeap))
 	{
-		relation_close(OldHeap, AccessExclusiveLock);
+		relation_close(OldHeap, lmode);
 		return false;
 	}
 
@@ -477,7 +820,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		 */
 		if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid)))
 		{
-			relation_close(OldHeap, AccessExclusiveLock);
+			relation_close(OldHeap, lmode);
 			return false;
 		}
 
@@ -488,7 +831,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		if ((options & CLUOPT_RECHECK_ISCLUSTERED) != 0 &&
 			!get_index_isclustered(indexOid))
 		{
-			relation_close(OldHeap, AccessExclusiveLock);
+			relation_close(OldHeap, lmode);
 			return false;
 		}
 	}
@@ -500,7 +843,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
  * Verify that the specified heap and index are valid to cluster on
  *
  * Side effect: obtains lock on the index.  The caller may
- * in some cases already have AccessExclusiveLock on the table, but
+ * in some cases already have a lock of the same strength on the table, but
  * not in all cases so we can't rely on the table-level lock for
  * protection here.
  */
@@ -629,18 +972,95 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
 	table_close(pg_index, RowExclusiveLock);
 }
 
+/*
+ * Check if the CONCURRENTLY option is legal for the relation.
+ *
+ * *Ident_idx_p receives OID of the identity index.
+ */
+static void
+check_repack_concurrently_requirements(Relation rel, Oid *ident_idx_p)
+{
+	char		relpersistence,
+				replident;
+	Oid			ident_idx;
+
+	/* Data changes in system relations are not logically decoded. */
+	if (IsCatalogRelation(rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot repack relation \"%s\"",
+						RelationGetRelationName(rel)),
+				 errhint("REPACK CONCURRENTLY is not supported for catalog relations.")));
+
+	/*
+	 * reorderbuffer.c does not seem to handle processing of TOAST relation
+	 * alone.
+	 */
+	if (IsToastRelation(rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot repack relation \"%s\"",
+						RelationGetRelationName(rel)),
+				 errhint("REPACK CONCURRENTLY is not supported for TOAST relations, unless the main relation is repacked too.")));
+
+	relpersistence = rel->rd_rel->relpersistence;
+	if (relpersistence != RELPERSISTENCE_PERMANENT)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot repack relation \"%s\"",
+						RelationGetRelationName(rel)),
+				 errhint("REPACK CONCURRENTLY is only allowed for permanent relations.")));
+
+	/* With NOTHING, WAL does not contain the old tuple. */
+	replident = rel->rd_rel->relreplident;
+	if (replident == REPLICA_IDENTITY_NOTHING)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot repack relation \"%s\"",
+						RelationGetRelationName(rel)),
+				 errhint("Relation \"%s\" has insufficient replication identity.",
+						 RelationGetRelationName(rel))));
+
+	/*
+	 * If the identity index is not set due to replica identity being, PK
+	 * might exist.
+	 */
+	ident_idx = RelationGetReplicaIndex(rel);
+	if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex))
+		ident_idx = rel->rd_pkindex;
+	if (!OidIsValid(ident_idx))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot process relation \"%s\"",
+						RelationGetRelationName(rel)),
+				 errhint("Relation \"%s\" has no identity index.",
+						 RelationGetRelationName(rel))));
+
+	*ident_idx_p = ident_idx;
+}
+
+
 /*
  * rebuild_relation: rebuild an existing relation in index or physical order
  *
- * OldHeap: table to rebuild.
+ * OldHeap: table to rebuild.  See cluster_rel() for comments on the required
+ * lock strength.
+ *
  * index: index to cluster by, or NULL to rewrite in physical order.
  *
- * On entry, heap and index (if one is given) must be open, and
- * AccessExclusiveLock held on them.
- * On exit, they are closed, but locks on them are not released.
+ * ident_idx: identity index, to handle replaying of concurrent data changes
+ * to the new heap. InvalidOid if there's no CONCURRENTLY option.
+ *
+ * On entry, heap and index (if one is given) must be open, and the
+ * appropriate lock held on them -- AccessExclusiveLock for exclusive
+ * processing and ShareUpdateExclusiveLock for concurrent processing.
+ *
+ * On exit, they are closed, but still locked with AccessExclusiveLock.
+ * (The function handles the lock upgrade if 'concurrent' is true.)
  */
 static void
-rebuild_relation(Relation OldHeap, Relation index, bool verbose)
+rebuild_relation(Relation OldHeap, Relation index, bool verbose,
+				 Oid ident_idx)
 {
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Oid			accessMethod = OldHeap->rd_rel->relam;
@@ -648,13 +1068,55 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose)
 	Oid			OIDNewHeap;
 	Relation	NewHeap;
 	char		relpersistence;
-	bool		is_system_catalog;
 	bool		swap_toast_by_content;
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
+	bool		concurrent = OidIsValid(ident_idx);
+	Snapshot	snapshot = NULL;
+#if USE_ASSERT_CHECKING
+	LOCKMODE	lmode;
+
+	lmode = concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock;
+
+	Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
+	Assert(index == NULL || CheckRelationLockedByMe(index, lmode, false));
+#endif
+
+	if (concurrent)
+	{
+		/*
+		 * The worker needs to be member of the locking group we're the leader
+		 * of. We ought to become the leader before the worker starts. The
+		 * worker will join the group as soon as it starts.
+		 *
+		 * This is to make sure that the deadlock described below is
+		 * detectable by deadlock.c: if the worker waits for a transaction to
+		 * complete and we are waiting for the worker output, then effectively
+		 * we (i.e. this backend) are waiting for that transaction.
+		 */
+		BecomeLockGroupLeader();
+
+		/*
+		 * Start the worker that decodes data changes applied while we're
+		 * copying the table contents.
+		 *
+		 * Note that the worker has to wait for all transactions with XID
+		 * already assigned to finish. If some of those transactions is
+		 * waiting for a lock conflicting with ShareUpdateExclusiveLock on our
+		 * table (e.g.  it runs CREATE INDEX), we can end up in a deadlock.
+		 * Not sure this risk is worth unlocking/locking the table (and its
+		 * clustering index) and checking again if it's still eligible for
+		 * REPACK CONCURRENTLY.
+		 */
+		start_decoding_worker(tableOid);
+
+		/*
+		 * Wait until the worker has the initial snapshot and retrieve it.
+		 */
+		snapshot = get_initial_snapshot(decoding_worker);
 
-	Assert(CheckRelationLockedByMe(OldHeap, AccessExclusiveLock, false) &&
-		   (index == NULL || CheckRelationLockedByMe(index, AccessExclusiveLock, false)));
+		PushActiveSnapshot(snapshot);
+	}
 
 	/* for CLUSTER or REPACK USING INDEX, mark the index as the one to use */
 	if (index != NULL)
@@ -662,7 +1124,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose)
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
-	is_system_catalog = IsSystemRelation(OldHeap);
 
 	/*
 	 * Create the transient table that will receive the re-ordered data.
@@ -678,30 +1139,59 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose)
 	NewHeap = table_open(OIDNewHeap, NoLock);
 
 	/* Copy the heap data into the new table in the desired order */
-	copy_table_data(NewHeap, OldHeap, index, verbose,
+	copy_table_data(NewHeap, OldHeap, index, snapshot, verbose,
 					&swap_toast_by_content, &frozenXid, &cutoffMulti);
 
+	/* The historic snapshot won't be needed anymore. */
+	if (snapshot)
+	{
+		PopActiveSnapshot();
+		UpdateActiveSnapshotCommandId();
+	}
 
-	/* Close relcache entries, but keep lock until transaction commit */
-	table_close(OldHeap, NoLock);
-	if (index)
-		index_close(index, NoLock);
+	if (concurrent)
+	{
+		Assert(!swap_toast_by_content);
 
-	/*
-	 * Close the new relation so it can be dropped as soon as the storage is
-	 * swapped. The relation is not visible to others, so no need to unlock it
-	 * explicitly.
-	 */
-	table_close(NewHeap, NoLock);
+		/*
+		 * Close the index, but keep the lock. Both heaps will be closed by
+		 * the following call.
+		 */
+		if (index)
+			index_close(index, NoLock);
 
-	/*
-	 * Swap the physical files of the target and transient tables, then
-	 * rebuild the target's indexes and throw away the transient table.
-	 */
-	finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog,
-					 swap_toast_by_content, false, true,
-					 frozenXid, cutoffMulti,
-					 relpersistence);
+		rebuild_relation_finish_concurrent(NewHeap, OldHeap, ident_idx,
+										   frozenXid, cutoffMulti);
+
+		pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+									 PROGRESS_REPACK_PHASE_FINAL_CLEANUP);
+	}
+	else
+	{
+		bool		is_system_catalog = IsSystemRelation(OldHeap);
+
+		/* Close relcache entries, but keep lock until transaction commit */
+		table_close(OldHeap, NoLock);
+		if (index)
+			index_close(index, NoLock);
+
+		/*
+		 * Close the new relation so it can be dropped as soon as the storage
+		 * is swapped. The relation is not visible to others, so no need to
+		 * unlock it explicitly.
+		 */
+		table_close(NewHeap, NoLock);
+
+		/*
+		 * Swap the physical files of the target and transient tables, then
+		 * rebuild the target's indexes and throw away the transient table.
+		 */
+		finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog,
+						 swap_toast_by_content, false, true,
+						 true,	/* reindex */
+						 frozenXid, cutoffMulti,
+						 relpersistence);
+	}
 }
 
 
@@ -836,15 +1326,18 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 /*
  * Do the physical copying of table data.
  *
+ * 'snapshot' and 'decoding_ctx': see table_relation_copy_for_cluster(). Pass
+ * iff concurrent processing is required.
+ *
  * There are three output parameters:
  * *pSwapToastByContent is set true if toast tables must be swapped by content.
  * *pFreezeXid receives the TransactionId used as freeze cutoff point.
  * *pCutoffMulti receives the MultiXactId used as a cutoff point.
  */
 static void
-copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verbose,
-				bool *pSwapToastByContent, TransactionId *pFreezeXid,
-				MultiXactId *pCutoffMulti)
+copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
+				Snapshot snapshot, bool verbose, bool *pSwapToastByContent,
+				TransactionId *pFreezeXid, MultiXactId *pCutoffMulti)
 {
 	Relation	relRelation;
 	HeapTuple	reltup;
@@ -861,6 +1354,10 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
 	char	   *nspname;
+	bool		concurrent = snapshot != NULL;
+	LOCKMODE	lmode;
+
+	lmode = concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock;
 
 	pg_rusage_init(&ru0);
 
@@ -889,7 +1386,7 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb
 	 * will be held till end of transaction.
 	 */
 	if (OldHeap->rd_rel->reltoastrelid)
-		LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock);
+		LockRelationOid(OldHeap->rd_rel->reltoastrelid, lmode);
 
 	/*
 	 * If both tables have TOAST tables, perform toast swap by content.  It is
@@ -898,7 +1395,8 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb
 	 * swap by links.  This is okay because swap by content is only essential
 	 * for system catalogs, and we don't support schema changes for them.
 	 */
-	if (OldHeap->rd_rel->reltoastrelid && NewHeap->rd_rel->reltoastrelid)
+	if (OldHeap->rd_rel->reltoastrelid && NewHeap->rd_rel->reltoastrelid &&
+		!concurrent)
 	{
 		*pSwapToastByContent = true;
 
@@ -919,6 +1417,10 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb
 		 * follow the toast pointers to the wrong place.  (It would actually
 		 * work for values copied over from the old toast table, but not for
 		 * any values that we toast which were previously not toasted.)
+		 *
+		 * This would not work with CONCURRENTLY because we may need to delete
+		 * TOASTed tuples from the new heap. With this hack, we'd delete them
+		 * from the old heap.
 		 */
 		NewHeap->rd_toastoid = OldHeap->rd_rel->reltoastrelid;
 	}
@@ -994,7 +1496,8 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb
 	 * values (e.g. because the AM doesn't use freezing).
 	 */
 	table_relation_copy_for_cluster(OldHeap, NewHeap, OldIndex, use_sort,
-									cutoffs.OldestXmin, &cutoffs.FreezeLimit,
+									cutoffs.OldestXmin, snapshot,
+									&cutoffs.FreezeLimit,
 									&cutoffs.MultiXactCutoff,
 									&num_tuples, &tups_vacuumed,
 									&tups_recently_dead);
@@ -1003,7 +1506,11 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb
 	*pFreezeXid = cutoffs.FreezeLimit;
 	*pCutoffMulti = cutoffs.MultiXactCutoff;
 
-	/* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */
+	/*
+	 * Reset rd_toastoid just to be tidy --- it shouldn't be looked at again.
+	 * In the CONCURRENTLY case, we need to set it again before applying the
+	 * concurrent changes.
+	 */
 	NewHeap->rd_toastoid = InvalidOid;
 
 	num_pages = RelationGetNumberOfBlocks(NewHeap);
@@ -1461,14 +1968,13 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 				 bool swap_toast_by_content,
 				 bool check_constraints,
 				 bool is_internal,
+				 bool reindex,
 				 TransactionId frozenXid,
 				 MultiXactId cutoffMulti,
 				 char newrelpersistence)
 {
 	ObjectAddress object;
 	Oid			mapped_tables[4];
-	int			reindex_flags;
-	ReindexParams reindex_params = {0};
 	int			i;
 
 	/* Report that we are now swapping relation files */
@@ -1494,39 +2000,47 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	if (is_system_catalog)
 		CacheInvalidateCatalog(OIDOldHeap);
 
-	/*
-	 * Rebuild each index on the relation (but not the toast table, which is
-	 * all-new at this point).  It is important to do this before the DROP
-	 * step because if we are processing a system catalog that will be used
-	 * during DROP, we want to have its indexes available.  There is no
-	 * advantage to the other order anyway because this is all transactional,
-	 * so no chance to reclaim disk space before commit.  We do not need a
-	 * final CommandCounterIncrement() because reindex_relation does it.
-	 *
-	 * Note: because index_build is called via reindex_relation, it will never
-	 * set indcheckxmin true for the indexes.  This is OK even though in some
-	 * sense we are building new indexes rather than rebuilding existing ones,
-	 * because the new heap won't contain any HOT chains at all, let alone
-	 * broken ones, so it can't be necessary to set indcheckxmin.
-	 */
-	reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE;
-	if (check_constraints)
-		reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
+	if (reindex)
+	{
+		int			reindex_flags;
+		ReindexParams reindex_params = {0};
 
-	/*
-	 * Ensure that the indexes have the same persistence as the parent
-	 * relation.
-	 */
-	if (newrelpersistence == RELPERSISTENCE_UNLOGGED)
-		reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED;
-	else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
-		reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT;
+		/*
+		 * Rebuild each index on the relation (but not the toast table, which
+		 * is all-new at this point).  It is important to do this before the
+		 * DROP step because if we are processing a system catalog that will
+		 * be used during DROP, we want to have its indexes available.  There
+		 * is no advantage to the other order anyway because this is all
+		 * transactional, so no chance to reclaim disk space before commit. We
+		 * do not need a final CommandCounterIncrement() because
+		 * reindex_relation does it.
+		 *
+		 * Note: because index_build is called via reindex_relation, it will
+		 * never set indcheckxmin true for the indexes.  This is OK even
+		 * though in some sense we are building new indexes rather than
+		 * rebuilding existing ones, because the new heap won't contain any
+		 * HOT chains at all, let alone broken ones, so it can't be necessary
+		 * to set indcheckxmin.
+		 */
+		reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE;
+		if (check_constraints)
+			reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
 
-	/* Report that we are now reindexing relations */
-	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
-								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
+		/*
+		 * Ensure that the indexes have the same persistence as the parent
+		 * relation.
+		 */
+		if (newrelpersistence == RELPERSISTENCE_UNLOGGED)
+			reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED;
+		else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
+			reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT;
 
-	reindex_relation(NULL, OIDOldHeap, reindex_flags, &reindex_params);
+		/* Report that we are now reindexing relations */
+		pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+									 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
+
+		reindex_relation(NULL, OIDOldHeap, reindex_flags, &reindex_params);
+	}
 
 	/* Report that we are now doing clean up */
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
@@ -1570,6 +2084,17 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	object.objectId = OIDNewHeap;
 	object.objectSubId = 0;
 
+	if (!reindex)
+	{
+		/*
+		 * Make sure the changes in pg_class are visible. This is especially
+		 * important if !swap_toast_by_content, so that the correct TOAST
+		 * relation is dropped. (reindex_relation() above did not help in this
+		 * case))
+		 */
+		CommandCounterIncrement();
+	}
+
 	/*
 	 * The new relation is local to our transaction and we know nothing
 	 * depends on it, so DROP_RESTRICT should be OK.
@@ -1609,7 +2134,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 
 			/* Get the associated valid index to be renamed */
 			toastidx = toast_get_valid_index(newrel->rd_rel->reltoastrelid,
-											 NoLock);
+											 AccessExclusiveLock);
 
 			/* rename the toast table ... */
 			snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u",
@@ -1880,7 +2405,8 @@ repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
  * case, if an index name is given, it's up to the caller to resolve it.
  */
 static Relation
-process_single_relation(RepackStmt *stmt, ClusterParams *params)
+process_single_relation(RepackStmt *stmt, LOCKMODE lockmode, bool isTopLevel,
+						ClusterParams *params)
 {
 	Relation	rel;
 	Oid			tableOid;
@@ -1897,13 +2423,9 @@ process_single_relation(RepackStmt *stmt, ClusterParams *params)
 				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				errmsg("ANALYZE option must be specified when a column list is provided"));
 
-	/*
-	 * Find, lock, and check permissions on the table.  We obtain
-	 * AccessExclusiveLock right away to avoid lock-upgrade hazard in the
-	 * single-transaction case.
-	 */
+	/* Find, lock, and check permissions on the table. */
 	tableOid = RangeVarGetRelidExtended(stmt->relation->relation,
-										AccessExclusiveLock,
+										lockmode,
 										0,
 										RangeVarCallbackMaintainsTable,
 										NULL);
@@ -1928,13 +2450,14 @@ process_single_relation(RepackStmt *stmt, ClusterParams *params)
 		return rel;
 	else
 	{
-		Oid			indexOid;
+		Oid			indexOid = InvalidOid;
 
 		indexOid = determine_clustered_index(rel, stmt->usingindex,
 											 stmt->indexname);
 		if (OidIsValid(indexOid))
-			check_index_is_clusterable(rel, indexOid, AccessExclusiveLock);
-		cluster_rel(stmt->command, rel, indexOid, params);
+			check_index_is_clusterable(rel, indexOid, lockmode);
+
+		cluster_rel(stmt->command, rel, indexOid, params, isTopLevel);
 
 		/*
 		 * Do an analyze, if requested.  We close the transaction and start a
@@ -2029,3 +2552,1569 @@ RepackCommandAsString(RepackCommand cmd)
 	}
 	return "???";				/* keep compiler quiet */
 }
+
+
+/*
+ * Is this backend performing logical decoding on behalf of REPACK
+ * (CONCURRENTLY) ?
+ */
+bool
+am_decoding_for_repack(void)
+{
+	return OidIsValid(repacked_rel_locator.relNumber);
+}
+
+/*
+ * Does the WAL record contain a data change that this backend does not need
+ * to decode on behalf of REPACK (CONCURRENTLY)?
+ */
+bool
+change_useless_for_repack(XLogRecordBuffer *buf)
+{
+	XLogReaderState *r = buf->record;
+	RelFileLocator locator;
+
+	/* TOAST locator should not be set unless the main is. */
+	Assert(!OidIsValid(repacked_rel_toast_locator.relNumber) ||
+		   OidIsValid(repacked_rel_locator.relNumber));
+
+	/*
+	 * Backends not involved in REPACK (CONCURRENTLY) should not do the
+	 * filtering.
+	 */
+	if (!am_decoding_for_repack())
+		return false;
+
+	/*
+	 * If the record does not contain the block 0, it's probably not INSERT /
+	 * UPDATE / DELETE. In any case, we do not have enough information to
+	 * filter the change out.
+	 */
+	if (!XLogRecGetBlockTagExtended(r, 0, &locator, NULL, NULL, NULL))
+		return false;
+
+	/*
+	 * Decode the change if it belongs to the table we are repacking, or if it
+	 * belongs to its TOAST relation.
+	 */
+	if (RelFileLocatorEquals(locator, repacked_rel_locator))
+		return false;
+	if (OidIsValid(repacked_rel_toast_locator.relNumber) &&
+		RelFileLocatorEquals(locator, repacked_rel_toast_locator))
+		return false;
+
+	/* Filter out changes of other tables. */
+	return true;
+}
+
+/*
+ * This function is much like pg_create_logical_replication_slot() except that
+ * the new slot is neither released (if anyone else could read changes from
+ * our slot, we could miss changes other backends do while we copy the
+ * existing data into temporary table), nor persisted (it's easier to handle
+ * crash by restarting all the work from scratch).
+ */
+static LogicalDecodingContext *
+setup_logical_decoding(Oid relid)
+{
+	Relation	rel;
+	Oid			toastrelid;
+	LogicalDecodingContext *ctx;
+	NameData	slotname;
+	RepackDecodingState *dstate;
+
+	/*
+	 * REPACK CONCURRENTLY is not allowed in a transaction block, so this
+	 * should never fire.
+	 */
+	Assert(!TransactionIdIsValid(GetTopTransactionIdIfAny()));
+
+	/*
+	 * Make sure we can use logical decoding.
+	 */
+	CheckSlotPermissions();
+	CheckLogicalDecodingRequirements();
+
+	/*
+	 * A single backend should not execute multiple REPACK commands at a time,
+	 * so use PID to make the slot unique.
+	 *
+	 * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
+	 */
+	snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
+	ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
+						  false);
+
+	EnsureLogicalDecodingEnabled();
+
+	/*
+	 * Neither prepare_write nor do_write callback nor update_progress is
+	 * useful for us.
+	 */
+	ctx = CreateInitDecodingContext(REPL_PLUGIN_NAME,
+									NIL,
+									true,
+									InvalidXLogRecPtr,
+									XL_ROUTINE(.page_read = read_local_xlog_page,
+											   .segment_open = wal_segment_open,
+											   .segment_close = wal_segment_close),
+									NULL, NULL, NULL);
+
+	/*
+	 * We don't have control on setting fast_forward, so at least check it.
+	 */
+	Assert(!ctx->fast_forward);
+
+	DecodingContextFindStartpoint(ctx);
+
+	/*
+	 * decode_concurrent_changes() needs non-blocking callback.
+	 */
+	ctx->reader->routine.page_read = read_local_xlog_page_no_wait;
+
+	/*
+	 * read_local_xlog_page_no_wait() needs to be able to indicate the end of
+	 * WAL.
+	 */
+	ctx->reader->private_data = MemoryContextAllocZero(ctx->context,
+													   sizeof(ReadLocalXLogPageNoWaitPrivate));
+
+
+	/* Some WAL records should have been read. */
+	Assert(ctx->reader->EndRecPtr != InvalidXLogRecPtr);
+
+	/*
+	 * Initialize repack_current_segment so that we can notice WAL segment
+	 * boundaries.
+	 */
+	XLByteToSeg(ctx->reader->EndRecPtr, repack_current_segment,
+				wal_segment_size);
+
+	dstate = palloc0_object(RepackDecodingState);
+	dstate->relid = relid;
+
+	/*
+	 * Tuple descriptor may be needed to flatten a tuple before we write it to
+	 * a file. A copy is needed because the decoding worker invalidates system
+	 * caches before it starts to do the actual work.
+	 */
+	rel = table_open(relid, AccessShareLock);
+	dstate->tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
+
+	/* Avoid logical decoding of other relations. */
+	repacked_rel_locator = rel->rd_locator;
+	toastrelid = rel->rd_rel->reltoastrelid;
+	if (OidIsValid(toastrelid))
+	{
+		Relation	toastrel;
+
+		/* Avoid logical decoding of other TOAST relations. */
+		toastrel = table_open(toastrelid, AccessShareLock);
+		repacked_rel_toast_locator = toastrel->rd_locator;
+		table_close(toastrel, AccessShareLock);
+	}
+	table_close(rel, AccessShareLock);
+
+	/* The file will be set as soon as we have it opened. */
+	dstate->file = NULL;
+
+	ctx->output_writer_private = dstate;
+
+	return ctx;
+}
+
+/*
+ * Decode logical changes from the WAL sequence and store them to a file.
+ *
+ * If true is returned, there is no more work for the worker.
+ */
+static bool
+decode_concurrent_changes(LogicalDecodingContext *ctx,
+						  DecodingWorkerShared *shared)
+{
+	RepackDecodingState *dstate;
+	XLogRecPtr	lsn_upto;
+	bool		done;
+	char		fname[MAXPGPATH];
+
+	dstate = (RepackDecodingState *) ctx->output_writer_private;
+
+	/* Open the output file. */
+	DecodingWorkerFileName(fname, shared->relid, shared->last_exported + 1);
+	dstate->file = BufFileCreateFileSet(&shared->sfs.fs, fname);
+
+	SpinLockAcquire(&shared->mutex);
+	lsn_upto = shared->lsn_upto;
+	done = shared->done;
+	SpinLockRelease(&shared->mutex);
+
+	while (true)
+	{
+		XLogRecord *record;
+		XLogSegNo	segno_new;
+		char	   *errm = NULL;
+		XLogRecPtr	end_lsn;
+
+		CHECK_FOR_INTERRUPTS();
+
+		record = XLogReadRecord(ctx->reader, &errm);
+		if (record)
+		{
+			LogicalDecodingProcessRecord(ctx, ctx->reader);
+
+			/*
+			 * If WAL segment boundary has been crossed, inform the decoding
+			 * system that the catalog_xmin can advance.
+			 *
+			 * TODO Does it make sense to confirm more often? Segment size
+			 * seems appropriate for restart_lsn (because less than a segment
+			 * cannot be recycled anyway), however more frequent checks might
+			 * be beneficial for catalog_xmin.
+			 */
+			end_lsn = ctx->reader->EndRecPtr;
+			XLByteToSeg(end_lsn, segno_new, wal_segment_size);
+			if (segno_new != repack_current_segment)
+			{
+				LogicalConfirmReceivedLocation(end_lsn);
+				elog(DEBUG1, "REPACK: confirmed receive location %X/%X",
+					 (uint32) (end_lsn >> 32), (uint32) end_lsn);
+				repack_current_segment = segno_new;
+			}
+		}
+		else
+		{
+			ReadLocalXLogPageNoWaitPrivate *priv;
+
+			if (errm)
+				ereport(ERROR, (errmsg("%s", errm)));
+
+			/*
+			 * In the decoding loop we do not want to get blocked when there
+			 * is no more WAL available, otherwise the loop would become
+			 * uninterruptible.
+			 */
+			priv = (ReadLocalXLogPageNoWaitPrivate *)
+				ctx->reader->private_data;
+			if (priv->end_of_wal)
+				/* Do not miss the end of WAL condition next time. */
+				priv->end_of_wal = false;
+			else
+				ereport(ERROR, (errmsg("could not read WAL record")));
+		}
+
+		/*
+		 * Whether we could read new record or not, keep checking if
+		 * 'lsn_upto' was specified.
+		 */
+		if (XLogRecPtrIsInvalid(lsn_upto))
+		{
+			SpinLockAcquire(&shared->mutex);
+			lsn_upto = shared->lsn_upto;
+			/* 'done' should be set at the same time as 'lsn_upto' */
+			done = shared->done;
+			SpinLockRelease(&shared->mutex);
+		}
+		if (!XLogRecPtrIsInvalid(lsn_upto) &&
+			ctx->reader->EndRecPtr >= lsn_upto)
+			break;
+
+		if (record == NULL)
+		{
+			int64		timeout = 0;
+			WaitLSNResult res;
+
+			/*
+			 * Before we retry reading, wait until new WAL is flushed.
+			 *
+			 * There is a race condition such that the backend executing
+			 * REPACK determines 'lsn_upto', but before it sets the shared
+			 * variable, we reach the end of WAL. In that case we'd need to
+			 * wait until the next WAL flush (unrelated to REPACK). Although
+			 * that should not be a problem in a busy system, it might be
+			 * noticeable in other cases, including regression tests (which
+			 * are not necessarily executed in parallel). Therefore it makes
+			 * sense to use timeout.
+			 *
+			 * If lsn_upto is valid, WAL records having LSN lower than that
+			 * should already have been flushed to disk.
+			 */
+			if (XLogRecPtrIsInvalid(lsn_upto))
+				timeout = 100L;
+			res = WaitForLSN(WAIT_LSN_TYPE_PRIMARY_FLUSH,
+							 ctx->reader->EndRecPtr + 1,
+							 timeout);
+			if (res != WAIT_LSN_RESULT_SUCCESS &&
+				res != WAIT_LSN_RESULT_TIMEOUT)
+				ereport(ERROR, (errmsg("waiting for WAL failed")));
+		}
+	}
+
+	/*
+	 * Close the file so we can make it available to the backend.
+	 */
+	BufFileClose(dstate->file);
+	dstate->file = NULL;
+	SpinLockAcquire(&shared->mutex);
+	shared->lsn_upto = InvalidXLogRecPtr;
+	shared->last_exported++;
+	SpinLockRelease(&shared->mutex);
+	ConditionVariableSignal(&shared->cv);
+
+	return done;
+}
+
+/*
+ * Apply changes stored in 'file'.
+ */
+static void
+apply_concurrent_changes(BufFile *file, ChangeDest *dest)
+{
+	char		kind;
+	uint32		t_len;
+	Relation	rel = dest->rel;
+	TupleTableSlot *index_slot,
+			   *ident_slot;
+	HeapTuple	tup_old = NULL;
+
+	/* TupleTableSlot is needed to pass the tuple to ExecInsertIndexTuples(). */
+	index_slot = MakeSingleTupleTableSlot(RelationGetDescr(rel),
+										  &TTSOpsHeapTuple);
+
+	/* A slot to fetch tuples from identity index. */
+	ident_slot = table_slot_create(rel, NULL);
+
+	while (true)
+	{
+		size_t		nread;
+		HeapTuple	tup,
+					tup_exist;
+
+		CHECK_FOR_INTERRUPTS();
+
+		nread = BufFileReadMaybeEOF(file, &kind, 1, true);
+		/* Are we done with the file? */
+		if (nread == 0)
+			break;
+
+		/* Read the tuple. */
+		BufFileReadExact(file, &t_len, sizeof(t_len));
+		tup = (HeapTuple) palloc(HEAPTUPLESIZE + t_len);
+		tup->t_data = (HeapTupleHeader) ((char *) tup + HEAPTUPLESIZE);
+		BufFileReadExact(file, tup->t_data, t_len);
+		tup->t_len = t_len;
+		ItemPointerSetInvalid(&tup->t_self);
+		tup->t_tableOid = RelationGetRelid(dest->rel);
+
+		if (kind == CHANGE_UPDATE_OLD)
+		{
+			Assert(tup_old == NULL);
+			tup_old = tup;
+		}
+		else if (kind == CHANGE_INSERT)
+		{
+			Assert(tup_old == NULL);
+
+			apply_concurrent_insert(rel, tup, dest->iistate, index_slot);
+
+			pfree(tup);
+		}
+		else if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE)
+		{
+			HeapTuple	tup_key;
+
+			if (kind == CHANGE_UPDATE_NEW)
+			{
+				tup_key = tup_old != NULL ? tup_old : tup;
+			}
+			else
+			{
+				Assert(tup_old == NULL);
+				tup_key = tup;
+			}
+
+			/*
+			 * Find the tuple to be updated or deleted.
+			 */
+			tup_exist = find_target_tuple(rel, dest, tup_key, ident_slot);
+			if (tup_exist == NULL)
+				elog(ERROR, "failed to find target tuple");
+
+			if (kind == CHANGE_UPDATE_NEW)
+				apply_concurrent_update(rel, tup, tup_exist, dest->iistate,
+										index_slot);
+			else
+				apply_concurrent_delete(rel, tup_exist);
+
+			if (tup_old != NULL)
+			{
+				pfree(tup_old);
+				tup_old = NULL;
+			}
+
+			pfree(tup);
+		}
+		else
+			elog(ERROR, "unrecognized kind of change: %d", kind);
+
+		/*
+		 * If a change was applied now, increment CID for next writes and
+		 * update the snapshot so it sees the changes we've applied so far.
+		 */
+		if (kind != CHANGE_UPDATE_OLD)
+		{
+			CommandCounterIncrement();
+			UpdateActiveSnapshotCommandId();
+		}
+	}
+
+	/* Cleanup. */
+	ExecDropSingleTupleTableSlot(index_slot);
+	ExecDropSingleTupleTableSlot(ident_slot);
+}
+
+static void
+apply_concurrent_insert(Relation rel, HeapTuple tup, IndexInsertState *iistate,
+						TupleTableSlot *index_slot)
+{
+	List	   *recheck;
+
+	/*
+	 * Like simple_heap_insert(), but make sure that the INSERT is not
+	 * logically decoded - see reform_and_rewrite_tuple() for more
+	 * information.
+	 */
+	heap_insert(rel, tup, GetCurrentCommandId(true), HEAP_INSERT_NO_LOGICAL,
+				NULL);
+
+	/*
+	 * Update indexes.
+	 */
+	ExecStoreHeapTuple(tup, index_slot, false);
+	recheck = ExecInsertIndexTuples(iistate->rri,
+									iistate->estate,
+									0,
+									index_slot,
+									NIL, NULL);
+
+	/*
+	 * If recheck is required, it must have been performed on the source
+	 * relation by now. (All the logical changes we process here are already
+	 * committed.)
+	 */
+	list_free(recheck);
+	ResetPerTupleExprContext(iistate->estate);
+
+	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
+}
+
+static void
+apply_concurrent_update(Relation rel, HeapTuple tup, HeapTuple tup_target,
+						IndexInsertState *iistate, TupleTableSlot *index_slot)
+{
+	LockTupleMode lockmode;
+	TM_FailureData tmfd;
+	TU_UpdateIndexes update_indexes;
+	TM_Result	res;
+	List	   *recheck;
+
+	/*
+	 * Write the new tuple into the new heap. ('tup' gets the TID assigned
+	 * here.)
+	 *
+	 * Do it like in simple_heap_update(), except for 'wal_logical' (and
+	 * except for 'wait').
+	 */
+	res = heap_update(rel, &tup_target->t_self, tup,
+					  GetCurrentCommandId(true),
+					  InvalidSnapshot,
+					  false,	/* no wait - only we are doing changes */
+					  &tmfd, &lockmode, &update_indexes,
+					  false /* wal_logical */ );
+	if (res != TM_Ok)
+		ereport(ERROR, (errmsg("failed to apply concurrent UPDATE")));
+
+	ExecStoreHeapTuple(tup, index_slot, false);
+
+	if (update_indexes != TU_None)
+	{
+		bits32		flags = EIIT_IS_UPDATE;
+
+		if (update_indexes == TU_Summarizing)
+			flags |= EIIT_ONLY_SUMMARIZING;
+		recheck = ExecInsertIndexTuples(iistate->rri,
+										iistate->estate,
+										flags,
+										index_slot,
+										NIL, NULL);
+		list_free(recheck);
+		ResetPerTupleExprContext(iistate->estate);
+	}
+
+	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
+}
+
+static void
+apply_concurrent_delete(Relation rel, HeapTuple tup_target)
+{
+	TM_Result	res;
+	TM_FailureData tmfd;
+
+	/*
+	 * Delete tuple from the new heap.
+	 *
+	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
+	 * except for 'wait').
+	 */
+	res = heap_delete(rel, &tup_target->t_self, GetCurrentCommandId(true),
+					  InvalidSnapshot, false,
+					  &tmfd,
+					  false,	/* no wait - only we are doing changes */
+					  false /* wal_logical */ );
+
+	if (res != TM_Ok)
+		ereport(ERROR, (errmsg("failed to apply concurrent DELETE")));
+
+	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_DELETED, 1);
+}
+
+/*
+ * Find the tuple to be updated or deleted.
+ *
+ * 'tup_key' is a tuple containing the key values for the scan.
+ */
+static HeapTuple
+find_target_tuple(Relation rel, ChangeDest *dest, HeapTuple tup_key,
+				  TupleTableSlot *ident_slot)
+{
+	Relation	ident_index = dest->ident_index;
+	IndexScanDesc scan;
+	Form_pg_index ident_form;
+	int2vector *ident_indkey;
+	HeapTuple	result = NULL;
+
+	/* XXX no instrumentation for now */
+	scan = index_beginscan(rel, ident_index, GetActiveSnapshot(),
+						   NULL, dest->ident_key_nentries, 0);
+
+	/* Info needed to retrieve key values from heap tuple. */
+	ident_form = ident_index->rd_index;
+	ident_indkey = &ident_form->indkey;
+
+	/*
+	 * Scan key is passed by caller, so it does not have to be constructed
+	 * multiple times. Key entries have all fields initialized, except for
+	 * sk_argument.
+	 *
+	 * Use the incoming tuple to finalize the scan key.
+	 */
+	for (int i = 0; i < dest->ident_key_nentries; i++)
+	{
+		ScanKey		entry;
+		bool		isnull;
+		int16		attno_heap;
+
+		entry = &dest->ident_key[i];
+		attno_heap = ident_indkey->values[i];
+		entry->sk_argument = heap_getattr(tup_key,
+										  attno_heap,
+										  rel->rd_att,
+										  &isnull);
+		Assert(!isnull);
+	}
+
+	index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0);
+	if (index_getnext_slot(scan, ForwardScanDirection, ident_slot))
+	{
+		bool		shouldFree;
+
+		result = ExecFetchSlotHeapTuple(ident_slot, false, &shouldFree);
+		/* TTSOpsBufferHeapTuple has .get_heap_tuple != NULL. */
+		Assert(!shouldFree);
+	}
+	index_endscan(scan);
+
+	return result;
+}
+
+/*
+ * Decode and apply concurrent changes, up to (and including) the record whose
+ * LSN is 'end_of_wal'.
+ */
+static void
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeDest *dest, bool done)
+{
+	DecodingWorkerShared *shared;
+	char		fname[MAXPGPATH];
+	BufFile    *file;
+
+	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+								 PROGRESS_REPACK_PHASE_CATCH_UP);
+
+	/* Ask the worker for the file. */
+	shared = (DecodingWorkerShared *) dsm_segment_address(decoding_worker->seg);
+	SpinLockAcquire(&shared->mutex);
+	shared->lsn_upto = end_of_wal;
+	shared->done = done;
+	SpinLockRelease(&shared->mutex);
+
+	/*
+	 * The worker needs to finish processing of the current WAL record. Even
+	 * if it's idle, it'll need to close the output file. Thus we're likely to
+	 * wait, so prepare for sleep.
+	 */
+	ConditionVariablePrepareToSleep(&shared->cv);
+	for (;;)
+	{
+		int			last_exported;
+
+		SpinLockAcquire(&shared->mutex);
+		last_exported = shared->last_exported;
+		SpinLockRelease(&shared->mutex);
+
+		/*
+		 * Has the worker exported the file we are waiting for?
+		 */
+		if (last_exported == dest->file_seq)
+			break;
+
+		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
+	}
+	ConditionVariableCancelSleep();
+
+	/* Open the file. */
+	DecodingWorkerFileName(fname, shared->relid, dest->file_seq);
+	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
+	apply_concurrent_changes(file, dest);
+
+	BufFileClose(file);
+
+	/* Get ready for the next file. */
+	dest->file_seq++;
+}
+
+/*
+ * Initialize IndexInsertState for index specified by ident_index_id.
+ *
+ * While doing that, also return the identity index in *ident_index_p.
+ */
+static IndexInsertState *
+get_index_insert_state(Relation relation, Oid ident_index_id,
+					   Relation *ident_index_p)
+{
+	EState	   *estate;
+	int			i;
+	IndexInsertState *result;
+	Relation	ident_index = NULL;
+
+	result = (IndexInsertState *) palloc0(sizeof(IndexInsertState));
+	estate = CreateExecutorState();
+
+	result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+	InitResultRelInfo(result->rri, relation, 0, 0, 0);
+	ExecOpenIndices(result->rri, false);
+
+	/*
+	 * Find the relcache entry of the identity index so that we spend no extra
+	 * effort to open / close it.
+	 */
+	for (i = 0; i < result->rri->ri_NumIndices; i++)
+	{
+		Relation	ind_rel;
+
+		ind_rel = result->rri->ri_IndexRelationDescs[i];
+		if (ind_rel->rd_id == ident_index_id)
+			ident_index = ind_rel;
+	}
+	if (ident_index == NULL)
+		elog(ERROR, "failed to open identity index");
+
+	/* Only initialize fields needed by ExecInsertIndexTuples(). */
+	result->estate = estate;
+
+	*ident_index_p = ident_index;
+	return result;
+}
+
+/*
+ * Build scan key to process logical changes.
+ */
+static ScanKey
+build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries)
+{
+	Relation	ident_idx_rel;
+	Form_pg_index ident_idx;
+	int			n,
+				i;
+	ScanKey		result;
+
+	Assert(OidIsValid(ident_idx_oid));
+	ident_idx_rel = index_open(ident_idx_oid, AccessShareLock);
+	ident_idx = ident_idx_rel->rd_index;
+	n = ident_idx->indnkeyatts;
+	result = (ScanKey) palloc(sizeof(ScanKeyData) * n);
+	for (i = 0; i < n; i++)
+	{
+		ScanKey		entry;
+		Oid			opfamily,
+					opcintype,
+					opno,
+					opcode;
+
+		entry = &result[i];
+
+		opfamily = ident_idx_rel->rd_opfamily[i];
+		opcintype = ident_idx_rel->rd_opcintype[i];
+		opno = get_opfamily_member(opfamily, opcintype, opcintype,
+								   BTEqualStrategyNumber);
+
+		if (!OidIsValid(opno))
+			elog(ERROR, "failed to find = operator for type %u", opcintype);
+
+		opcode = get_opcode(opno);
+		if (!OidIsValid(opcode))
+			elog(ERROR, "failed to find = operator for operator %u", opno);
+
+		/* Initialize everything but argument. */
+		ScanKeyInit(entry,
+					i + 1,
+					BTEqualStrategyNumber, opcode,
+					(Datum) NULL);
+		entry->sk_collation = ident_idx_rel->rd_indcollation[i];
+	}
+	index_close(ident_idx_rel, AccessShareLock);
+
+	*nentries = n;
+	return result;
+}
+
+static void
+free_index_insert_state(IndexInsertState *iistate)
+{
+	ExecCloseIndices(iistate->rri);
+	FreeExecutorState(iistate->estate);
+	pfree(iistate->rri);
+	pfree(iistate);
+}
+
+static void
+cleanup_logical_decoding(LogicalDecodingContext *ctx)
+{
+	RepackDecodingState *dstate;
+
+	dstate = (RepackDecodingState *) ctx->output_writer_private;
+
+	FreeTupleDesc(dstate->tupdesc);
+	FreeDecodingContext(ctx);
+
+	ReplicationSlotDropAcquired();
+	pfree(dstate);
+}
+
+/*
+ * The final steps of rebuild_relation() for concurrent processing.
+ *
+ * On entry, NewHeap is locked in AccessExclusiveLock mode. OldHeap and its
+ * clustering index (if one is passed) are still locked in a mode that allows
+ * concurrent data changes. On exit, both tables and their indexes are closed,
+ * but locked in AccessExclusiveLock mode.
+ */
+static void
+rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
+								   Oid identIdx, TransactionId frozenXid,
+								   MultiXactId cutoffMulti)
+{
+	LOCKMODE	lockmode_old PG_USED_FOR_ASSERTS_ONLY;
+	List	   *ind_oids_new;
+	Oid			old_table_oid = RelationGetRelid(OldHeap);
+	Oid			new_table_oid = RelationGetRelid(NewHeap);
+	List	   *ind_oids_old = RelationGetIndexList(OldHeap);
+	ListCell   *lc,
+			   *lc2;
+	char		relpersistence;
+	bool		is_system_catalog;
+	Oid			ident_idx_new;
+	XLogRecPtr	wal_insert_ptr,
+				end_of_wal;
+	char		dummy_rec_data = '\0';
+	Relation   *ind_refs,
+			   *ind_refs_p;
+	int			nind;
+	ChangeDest	chgdst;
+
+	/* Like in cluster_rel(). */
+	lockmode_old = ShareUpdateExclusiveLock;
+	Assert(CheckRelationLockedByMe(OldHeap, lockmode_old, false));
+	/* This is expected from the caller. */
+	Assert(CheckRelationLockedByMe(NewHeap, AccessExclusiveLock, false));
+
+	/*
+	 * Unlike the exclusive case, we build new indexes for the new relation
+	 * rather than swapping the storage and reindexing the old relation. The
+	 * point is that the index build can take some time, so we do it before we
+	 * get AccessExclusiveLock on the old heap and therefore we cannot swap
+	 * the heap storage yet.
+	 *
+	 * index_create() will lock the new indexes using AccessExclusiveLock - no
+	 * need to change that. At the same time, we use ShareUpdateExclusiveLock
+	 * to lock the existing indexes - that should be enough to prevent others
+	 * from changing them while we're repacking the relation. The lock on
+	 * table should prevent others from changing the index column list, but
+	 * might not be enough for commands like ALTER INDEX ... SET ... (Those
+	 * are not necessarily dangerous, but can make user confused if the
+	 * changes they do get lost due to REPACK.)
+	 */
+	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
+
+	/* Find "identity index" on the new relation. */
+	ident_idx_new = InvalidOid;
+	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	{
+		Oid			ind_old = lfirst_oid(lc);
+		Oid			ind_new = lfirst_oid(lc2);
+
+		if (identIdx == ind_old)
+		{
+			ident_idx_new = ind_new;
+			break;
+		}
+	}
+	if (!OidIsValid(ident_idx_new))
+
+		/*
+		 * Should not happen, given our lock on the old relation.
+		 */
+		ereport(ERROR,
+				(errmsg("identity index missing on the new relation")));
+
+	/* Gather information to apply concurrent changes. */
+	chgdst.rel = NewHeap;
+	chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new,
+											&chgdst.ident_index);
+	chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap,
+										  &chgdst.ident_key_nentries);
+	chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1;
+
+	/*
+	 * During testing, wait for another backend to perform concurrent data
+	 * changes which we will process below.
+	 */
+	INJECTION_POINT("repack-concurrently-before-lock", NULL);
+
+	/*
+	 * Flush all WAL records inserted so far (possibly except for the last
+	 * incomplete page, see GetInsertRecPtr), to minimize the amount of data
+	 * we need to flush while holding exclusive lock on the source table.
+	 */
+	wal_insert_ptr = GetInsertRecPtr();
+	XLogFlush(wal_insert_ptr);
+	end_of_wal = GetFlushRecPtr(NULL);
+
+	/*
+	 * Apply concurrent changes first time, to minimize the time we need to
+	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
+	 * written during the data copying and index creation.)
+	 */
+	process_concurrent_changes(end_of_wal, &chgdst, false);
+
+	/*
+	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
+	 * is one), all its indexes, so that we can swap the files.
+	 */
+	LockRelationOid(old_table_oid, AccessExclusiveLock);
+
+	/*
+	 * Lock all indexes now, not only the clustering one: all indexes need to
+	 * have their files swapped. While doing that, store their relation
+	 * references in an array, to handle predicate locks below.
+	 */
+	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
+	nind = 0;
+	foreach_oid(ind_oid, ind_oids_old)
+	{
+		Relation	index;
+
+		index = index_open(ind_oid, AccessExclusiveLock);
+
+		/*
+		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
+		 * new index was created in build_new_indexes()? 2) Specifically for
+		 * the clustering index, should check_index_is_clusterable() be called
+		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
+		 * table probably blocks all commands that affect the result of
+		 * check_index_is_clusterable().)
+		 */
+		*ind_refs_p = index;
+		ind_refs_p++;
+		nind++;
+	}
+
+	/*
+	 * Lock the OldHeap's TOAST relation exclusively - again, the lock is
+	 * needed to swap the files.
+	 */
+	if (OidIsValid(OldHeap->rd_rel->reltoastrelid))
+		LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock);
+
+	/*
+	 * Tuples and pages of the old heap will be gone, but the heap will stay.
+	 */
+	TransferPredicateLocksToHeapRelation(OldHeap);
+	/* The same for indexes. */
+	for (int i = 0; i < nind; i++)
+	{
+		Relation	index = ind_refs[i];
+
+		TransferPredicateLocksToHeapRelation(index);
+
+		/*
+		 * References to indexes on the old relation are not needed anymore,
+		 * however locks stay till the end of the transaction.
+		 */
+		index_close(index, NoLock);
+	}
+	pfree(ind_refs);
+
+	/*
+	 * Flush anything we see in WAL, to make sure that all changes committed
+	 * while we were waiting for the exclusive lock are available for
+	 * decoding. This should not be necessary if all backends had
+	 * synchronous_commit set, but we can't rely on this setting.
+	 *
+	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
+	 * position, and GetLastImportantRecPtr() points at the start of the last
+	 * record rather than at the end. Thus the simplest way to determine the
+	 * insert position is to insert a dummy record and use its LSN.
+	 *
+	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
+	 * last record (plus the total size of all the page headers the record
+	 * spans)?
+	 */
+	XLogBeginInsert();
+	XLogRegisterData(&dummy_rec_data, 1);
+	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
+	XLogFlush(wal_insert_ptr);
+	end_of_wal = GetFlushRecPtr(NULL);
+
+	/*
+	 * Apply the concurrent changes again. Indicate that the decoding worker
+	 * won't be needed anymore.
+	 */
+	process_concurrent_changes(end_of_wal, &chgdst, true);
+
+	/* Remember info about rel before closing OldHeap */
+	relpersistence = OldHeap->rd_rel->relpersistence;
+	is_system_catalog = IsSystemRelation(OldHeap);
+
+	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+								 PROGRESS_REPACK_PHASE_SWAP_REL_FILES);
+
+	/*
+	 * Even ShareUpdateExclusiveLock should have prevented others from
+	 * creating / dropping indexes (even using the CONCURRENTLY option), so we
+	 * do not need to check whether the lists match.
+	 */
+	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	{
+		Oid			ind_old = lfirst_oid(lc);
+		Oid			ind_new = lfirst_oid(lc2);
+		Oid			mapped_tables[4];
+
+		/* Zero out possible results from swapped_relation_files */
+		memset(mapped_tables, 0, sizeof(mapped_tables));
+
+		swap_relation_files(ind_old, ind_new,
+							(old_table_oid == RelationRelationId),
+							false,	/* swap_toast_by_content */
+							true,
+							InvalidTransactionId,
+							InvalidMultiXactId,
+							mapped_tables);
+
+#ifdef USE_ASSERT_CHECKING
+
+		/*
+		 * Concurrent processing is not supported for system relations, so
+		 * there should be no mapped tables.
+		 */
+		for (int i = 0; i < 4; i++)
+			Assert(mapped_tables[i] == 0);
+#endif
+	}
+
+	/* The new indexes must be visible for deletion. */
+	CommandCounterIncrement();
+
+	/* Close the old heap but keep lock until transaction commit. */
+	table_close(OldHeap, NoLock);
+	/* Close the new heap. (We didn't have to open its indexes). */
+	table_close(NewHeap, NoLock);
+
+	/* Cleanup what we don't need anymore. (And close the identity index.) */
+	pfree(chgdst.ident_key);
+	free_index_insert_state(chgdst.iistate);
+
+	/*
+	 * Swap the relations and their TOAST relations and TOAST indexes. This
+	 * also drops the new relation and its indexes.
+	 *
+	 * (System catalogs are currently not supported.)
+	 */
+	Assert(!is_system_catalog);
+	finish_heap_swap(old_table_oid, new_table_oid,
+					 is_system_catalog,
+					 false,		/* swap_toast_by_content */
+					 false,
+					 true,
+					 false,		/* reindex */
+					 frozenXid, cutoffMulti,
+					 relpersistence);
+}
+
+/*
+ * Build indexes on NewHeap according to those on OldHeap.
+ *
+ * OldIndexes is the list of index OIDs on OldHeap. The contained indexes end
+ * up locked using ShareUpdateExclusiveLock.
+ *
+ * A list of OIDs of the corresponding indexes created on NewHeap is
+ * returned. The order of items does match, so we can use these arrays to swap
+ * index storage.
+ */
+static List *
+build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
+{
+	List	   *result = NIL;
+
+	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
+
+	foreach_oid(ind_oid, OldIndexes)
+	{
+		Oid			ind_oid_new;
+		char	   *newName;
+		Relation	ind;
+
+		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+
+		newName = ChooseRelationName(get_rel_name(ind_oid),
+									 NULL,
+									 "repacknew",
+									 get_rel_namespace(ind->rd_index->indrelid),
+									 false);
+		ind_oid_new = index_create_copy(NewHeap, ind_oid,
+										ind->rd_rel->reltablespace, newName,
+										false);
+		result = lappend_oid(result, ind_oid_new);
+
+		index_close(ind, NoLock);
+	}
+
+	return result;
+}
+
+/*
+ * Try to start a background worker to perform logical decoding of data
+ * changes applied to relation while REPACK CONCURRENTLY is copying its
+ * contents to a new table.
+ */
+static void
+start_decoding_worker(Oid relid)
+{
+	Size		size;
+	dsm_segment *seg;
+	DecodingWorkerShared *shared;
+	shm_mq	   *mq;
+	shm_mq_handle *mqh;
+	BackgroundWorker bgw;
+
+	/* Setup shared memory. */
+	size = BUFFERALIGN(offsetof(DecodingWorkerShared, error_queue)) +
+		BUFFERALIGN(REPACK_ERROR_QUEUE_SIZE);
+	seg = dsm_create(size, 0);
+	shared = (DecodingWorkerShared *) dsm_segment_address(seg);
+	shared->lsn_upto = InvalidXLogRecPtr;
+	shared->done = false;
+	SharedFileSetInit(&shared->sfs, seg);
+	shared->last_exported = -1;
+	SpinLockInit(&shared->mutex);
+	shared->dbid = MyDatabaseId;
+
+	/*
+	 * This is the UserId set in cluster_rel(). Security context shouldn't be
+	 * needed for decoding worker.
+	 */
+	shared->roleid = GetUserId();
+	shared->relid = relid;
+	ConditionVariableInit(&shared->cv);
+	shared->backend_proc = MyProc;
+	shared->backend_pid = MyProcPid;
+	shared->backend_proc_number = MyProcNumber;
+
+	mq = shm_mq_create((char *) BUFFERALIGN(shared->error_queue),
+					   REPACK_ERROR_QUEUE_SIZE);
+	shm_mq_set_receiver(mq, MyProc);
+	mqh = shm_mq_attach(mq, seg, NULL);
+
+	memset(&bgw, 0, sizeof(bgw));
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "REPACK decoding worker for relation \"%s\"",
+			 get_rel_name(relid));
+	snprintf(bgw.bgw_type, BGW_MAXLEN, "REPACK decoding worker");
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+	bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
+	bgw.bgw_restart_time = BGW_NEVER_RESTART;
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "RepackWorkerMain");
+	bgw.bgw_main_arg = UInt32GetDatum(dsm_segment_handle(seg));
+	bgw.bgw_notify_pid = MyProcPid;
+
+	decoding_worker = palloc0_object(DecodingWorker);
+	if (!RegisterDynamicBackgroundWorker(&bgw, &decoding_worker->handle))
+		ereport(ERROR,
+				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+				 errmsg("out of background worker slots"),
+				 errhint("You might need to increase \"%s\".", "max_worker_processes")));
+
+	decoding_worker->seg = seg;
+	decoding_worker->error_mqh = mqh;
+
+	/*
+	 * The decoding setup must be done before the caller can have XID assigned
+	 * for any reason, otherwise the worker might end up in a deadlock,
+	 * waiting for the caller's transaction to end. Therefore wait here until
+	 * the worker indicates that it has the logical decoding initialized.
+	 */
+	ConditionVariablePrepareToSleep(&shared->cv);
+	for (;;)
+	{
+		bool		initialized;
+
+		SpinLockAcquire(&shared->mutex);
+		initialized = shared->initialized;
+		SpinLockRelease(&shared->mutex);
+
+		if (initialized)
+			break;
+
+		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
+	}
+	ConditionVariableCancelSleep();
+}
+
+/*
+ * Stop the decoding worker and cleanup the related resources.
+ *
+ * The worker stops on its own when it knows there is no more work to do, but
+ * we need to stop it explicitly at least on ERROR in the launching backend.
+ */
+static void
+stop_decoding_worker(void)
+{
+	BgwHandleStatus status;
+
+	/* Haven't reached the worker startup? */
+	if (decoding_worker == NULL)
+		return;
+
+	/* Could not register the worker? */
+	if (decoding_worker->handle == NULL)
+		return;
+
+	TerminateBackgroundWorker(decoding_worker->handle);
+	/* The worker should really exit before the REPACK command does. */
+	HOLD_INTERRUPTS();
+	status = WaitForBackgroundWorkerShutdown(decoding_worker->handle);
+	RESUME_INTERRUPTS();
+
+	if (status == BGWH_POSTMASTER_DIED)
+		ereport(FATAL,
+				(errcode(ERRCODE_ADMIN_SHUTDOWN),
+				 errmsg("postmaster exited during REPACK command")));
+
+	shm_mq_detach(decoding_worker->error_mqh);
+
+	/*
+	 * If we could not cancel the current sleep due to ERROR, do that before
+	 * we detach from the shared memory the condition variable is located in.
+	 * If we did not, the bgworker ERROR handling code would try and fail
+	 * badly.
+	 */
+	ConditionVariableCancelSleep();
+
+	dsm_detach(decoding_worker->seg);
+	pfree(decoding_worker);
+	decoding_worker = NULL;
+}
+
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
+
+/*
+ * See ParallelWorkerShutdown for details.
+ */
+static void
+RepackWorkerShutdown(int code, Datum arg)
+{
+	SendProcSignal(backend_pid,
+				   PROCSIG_REPACK_MESSAGE,
+				   backend_proc_number);
+
+	dsm_detach((dsm_segment *) DatumGetPointer(arg));
+}
+
+/* REPACK decoding worker entry point */
+void
+RepackWorkerMain(Datum main_arg)
+{
+	dsm_segment *seg;
+	DecodingWorkerShared *shared;
+	shm_mq	   *mq;
+	shm_mq_handle *mqh;
+
+	is_repack_worker = true;
+
+	/*
+	 * Override the default bgworker_die() with die() so we can use
+	 * CHECK_FOR_INTERRUPTS().
+	 */
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	seg = dsm_attach(DatumGetUInt32(main_arg));
+	if (seg == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("could not map dynamic shared memory segment")));
+
+	shared = (DecodingWorkerShared *) dsm_segment_address(seg);
+
+	/* Arrange to signal the leader if we exit. */
+	backend_pid = shared->backend_pid;
+	backend_proc_number = shared->backend_proc_number;
+	before_shmem_exit(RepackWorkerShutdown, PointerGetDatum(seg));
+
+	/*
+	 * Join locking group - see the comments around the call of
+	 * start_decoding_worker().
+	 */
+	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
+		/* The leader is not running anymore. */
+		return;
+
+	/*
+	 * Setup a queue to send error messages to the backend that launched this
+	 * worker.
+	 */
+	mq = (shm_mq *) (char *) BUFFERALIGN(shared->error_queue);
+	shm_mq_set_sender(mq, MyProc);
+	mqh = shm_mq_attach(mq, seg, NULL);
+	pq_redirect_to_shm_mq(seg, mqh);
+	pq_set_parallel_leader(shared->backend_pid,
+						   shared->backend_proc_number);
+
+	/* Connect to the database. */
+	BackgroundWorkerInitializeConnectionByOid(shared->dbid, shared->roleid, 0);
+
+	repack_worker_internal(seg);
+}
+
+static void
+repack_worker_internal(dsm_segment *seg)
+{
+	DecodingWorkerShared *shared;
+	LogicalDecodingContext *decoding_ctx;
+	SharedFileSet *sfs;
+	Snapshot	snapshot;
+
+	/*
+	 * Transaction is needed to open relation, and it also provides us with a
+	 * resource owner.
+	 */
+	StartTransactionCommand();
+
+	shared = (DecodingWorkerShared *) dsm_segment_address(seg);
+
+	/*
+	 * Not sure the spinlock is needed here - the backend should not change
+	 * anything in the shared memory until we have serialized the snapshot.
+	 */
+	SpinLockAcquire(&shared->mutex);
+	Assert(XLogRecPtrIsInvalid(shared->lsn_upto));
+	sfs = &shared->sfs;
+	SpinLockRelease(&shared->mutex);
+
+	SharedFileSetAttach(sfs, seg);
+
+	/*
+	 * Prepare to capture the concurrent data changes ourselves.
+	 */
+	decoding_ctx = setup_logical_decoding(shared->relid);
+
+	/* Announce that we're ready. */
+	SpinLockAcquire(&shared->mutex);
+	shared->initialized = true;
+	SpinLockRelease(&shared->mutex);
+	ConditionVariableSignal(&shared->cv);
+
+	/* Build the initial snapshot and export it. */
+	snapshot = SnapBuildInitialSnapshot(decoding_ctx->snapshot_builder, true);
+	export_initial_snapshot(snapshot, shared);
+
+	/*
+	 * Only historic snapshots should be used now. Do not let us restrict the
+	 * progress of xmin horizon.
+	 */
+	InvalidateCatalogSnapshot();
+
+	while (!decode_concurrent_changes(decoding_ctx, shared))
+		;
+
+	/* Cleanup. */
+	cleanup_logical_decoding(decoding_ctx);
+	CommitTransactionCommand();
+}
+
+/*
+ * Make snapshot available to the backend that launched the decoding worker.
+ */
+static void
+export_initial_snapshot(Snapshot snapshot, DecodingWorkerShared *shared)
+{
+	char		fname[MAXPGPATH];
+	BufFile    *file;
+	Size		snap_size;
+	char	   *snap_space;
+
+	snap_size = EstimateSnapshotSpace(snapshot);
+	snap_space = (char *) palloc(snap_size);
+	SerializeSnapshot(snapshot, snap_space);
+	FreeSnapshot(snapshot);
+
+	DecodingWorkerFileName(fname, shared->relid, shared->last_exported + 1);
+	file = BufFileCreateFileSet(&shared->sfs.fs, fname);
+	/* To make restoration easier, write the snapshot size first. */
+	BufFileWrite(file, &snap_size, sizeof(snap_size));
+	BufFileWrite(file, snap_space, snap_size);
+	pfree(snap_space);
+	BufFileClose(file);
+
+	/* Increase the counter to tell the backend that the file is available. */
+	SpinLockAcquire(&shared->mutex);
+	shared->last_exported++;
+	SpinLockRelease(&shared->mutex);
+	ConditionVariableSignal(&shared->cv);
+}
+
+/*
+ * Get the initial snapshot from the decoding worker.
+ */
+static Snapshot
+get_initial_snapshot(DecodingWorker *worker)
+{
+	DecodingWorkerShared *shared;
+	char		fname[MAXPGPATH];
+	BufFile    *file;
+	Size		snap_size;
+	char	   *snap_space;
+	Snapshot	snapshot;
+
+	shared = (DecodingWorkerShared *) dsm_segment_address(worker->seg);
+
+	/*
+	 * The worker needs to initialize the logical decoding, which usually
+	 * takes some time. Therefore it makes sense to prepare for the sleep
+	 * first.
+	 */
+	ConditionVariablePrepareToSleep(&shared->cv);
+	for (;;)
+	{
+		int			last_exported;
+
+		SpinLockAcquire(&shared->mutex);
+		last_exported = shared->last_exported;
+		SpinLockRelease(&shared->mutex);
+
+		/*
+		 * Has the worker exported the file we are waiting for?
+		 */
+		if (last_exported == WORKER_FILE_SNAPSHOT)
+			break;
+
+		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
+	}
+	ConditionVariableCancelSleep();
+
+	/* Read the snapshot from a file. */
+	DecodingWorkerFileName(fname, shared->relid, WORKER_FILE_SNAPSHOT);
+	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
+	BufFileReadExact(file, &snap_size, sizeof(snap_size));
+	snap_space = (char *) palloc(snap_size);
+	BufFileReadExact(file, snap_space, snap_size);
+	BufFileClose(file);
+
+	/* Restore it. */
+	snapshot = RestoreSnapshot(snap_space);
+	pfree(snap_space);
+
+	return snapshot;
+}
+
+bool
+IsRepackWorker(void)
+{
+	return is_repack_worker;
+}
+
+/*
+ * Handle receipt of an interrupt indicating a repack worker message.
+ *
+ * Note: this is called within a signal handler!  All we can do is set
+ * a flag that will cause the next CHECK_FOR_INTERRUPTS() to invoke
+ * ProcessRepackMessages().
+ */
+void
+HandleRepackMessageInterrupt(void)
+{
+	InterruptPending = true;
+	RepackMessagePending = true;
+	SetLatch(MyLatch);
+}
+
+/*
+ * Process any queued protocol messages received from parallel workers.
+ */
+void
+ProcessRepackMessages(void)
+{
+	MemoryContext oldcontext;
+
+	static MemoryContext hpm_context = NULL;
+
+	/*
+	 * Nothing to do if we haven't launched the worker yet or have already
+	 * terminated it.
+	 */
+	if (decoding_worker == NULL)
+		return;
+
+	/*
+	 * This is invoked from ProcessInterrupts(), and since some of the
+	 * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential
+	 * for recursive calls if more signals are received while this runs.  It's
+	 * unclear that recursive entry would be safe, and it doesn't seem useful
+	 * even if it is safe, so let's block interrupts until done.
+	 */
+	HOLD_INTERRUPTS();
+
+	/*
+	 * Moreover, CurrentMemoryContext might be pointing almost anywhere.  We
+	 * don't want to risk leaking data into long-lived contexts, so let's do
+	 * our work here in a private context that we can reset on each use.
+	 */
+	if (hpm_context == NULL)	/* first time through? */
+		hpm_context = AllocSetContextCreate(TopMemoryContext,
+											"ProcessRepackMessages",
+											ALLOCSET_DEFAULT_SIZES);
+	else
+		MemoryContextReset(hpm_context);
+
+	oldcontext = MemoryContextSwitchTo(hpm_context);
+
+	/* OK to process messages.  Reset the flag saying there are more to do. */
+	RepackMessagePending = false;
+
+	/*
+	 * Read as many messages as we can from each worker, but stop when no more
+	 * messages can be read from the worker without blocking.
+	 */
+	while (true)
+	{
+		shm_mq_result res;
+		Size		nbytes;
+		void	   *data;
+
+		res = shm_mq_receive(decoding_worker->error_mqh, &nbytes,
+							 &data, true);
+		if (res == SHM_MQ_WOULD_BLOCK)
+			break;
+		else if (res == SHM_MQ_SUCCESS)
+		{
+			StringInfoData msg;
+
+			initStringInfo(&msg);
+			appendBinaryStringInfo(&msg, data, nbytes);
+			ProcessRepackMessage(&msg);
+			pfree(msg.data);
+		}
+		else
+		{
+			/*
+			 * The decoding worker is special in that it exits as soon as it
+			 * has its work done. Thus the DETACHED result code is fine.
+			 */
+			Assert(res == SHM_MQ_DETACHED);
+
+			break;
+		}
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Might as well clear the context on our way out */
+	MemoryContextReset(hpm_context);
+
+	RESUME_INTERRUPTS();
+}
+
+/*
+ * Process a single protocol message received from a single parallel worker.
+ */
+static void
+ProcessRepackMessage(StringInfo msg)
+{
+	char		msgtype;
+
+	msgtype = pq_getmsgbyte(msg);
+
+	switch (msgtype)
+	{
+		case PqMsg_ErrorResponse:
+		case PqMsg_NoticeResponse:
+			{
+				ErrorData	edata;
+
+				/* Parse ErrorResponse or NoticeResponse. */
+				pq_parse_errornotice(msg, &edata);
+
+				/* Death of a worker isn't enough justification for suicide. */
+				edata.elevel = Min(edata.elevel, ERROR);
+
+				/*
+				 * If desired, add a context line to show that this is a
+				 * message propagated from a parallel worker.  Otherwise, it
+				 * can sometimes be confusing to understand what actually
+				 * happened.
+				 */
+				if (edata.context)
+					edata.context = psprintf("%s\n%s", edata.context,
+											 _("decoding worker"));
+				else
+					edata.context = pstrdup(_("decoding worker"));
+
+				/* Rethrow error or print notice. */
+				ThrowErrorData(&edata);
+
+				break;
+			}
+
+		default:
+			{
+				elog(ERROR, "unrecognized message type received from decoding worker: %c (message length %d bytes)",
+					 msgtype, msg->len);
+			}
+	}
+}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 81a55a33ef2..539969d6eef 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -893,6 +893,7 @@ static void
 refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence)
 {
 	finish_heap_swap(matviewOid, OIDNewHeap, false, false, true, true,
+					 true,		/* reindex */
 					 RecentXmin, ReadNextMultiXactId(), relpersistence);
 }
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 85242dcc245..895e6992b89 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6035,6 +6035,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 			finish_heap_swap(tab->relid, OIDNewHeap,
 							 false, false, true,
 							 !OidIsValid(tab->newTableSpace),
+							 true,	/* reindex */
 							 RecentXmin,
 							 ReadNextMultiXactId(),
 							 persistence);
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index bce3a2daa24..201835f30a4 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -127,7 +127,7 @@ static void vac_truncate_clog(TransactionId frozenXID,
 							  TransactionId lastSaneFrozenXid,
 							  MultiXactId lastSaneMinMulti);
 static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
-					   BufferAccessStrategy bstrategy);
+					   BufferAccessStrategy bstrategy, bool isTopLevel);
 static double compute_parallel_delay(void);
 static VacOptValue get_vacoptval_from_boolean(DefElem *def);
 static bool vac_tid_reaped(ItemPointer itemptr, void *state);
@@ -630,7 +630,8 @@ vacuum(List *relations, const VacuumParams params, BufferAccessStrategy bstrateg
 
 			if (params.options & VACOPT_VACUUM)
 			{
-				if (!vacuum_rel(vrel->oid, vrel->relation, params, bstrategy))
+				if (!vacuum_rel(vrel->oid, vrel->relation, params, bstrategy,
+								isTopLevel))
 					continue;
 			}
 
@@ -2004,7 +2005,7 @@ vac_truncate_clog(TransactionId frozenXID,
  */
 static bool
 vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
-		   BufferAccessStrategy bstrategy)
+		   BufferAccessStrategy bstrategy, bool isTopLevel)
 {
 	LOCKMODE	lmode;
 	Relation	rel;
@@ -2295,7 +2296,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 
 			/* VACUUM FULL is a variant of REPACK; see cluster.c */
 			cluster_rel(REPACK_COMMAND_VACUUMFULL, rel, InvalidOid,
-						&cluster_params);
+						&cluster_params, isTopLevel);
 			/* cluster_rel closes the relation, but keeps lock */
 
 			rel = NULL;
@@ -2338,7 +2339,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
 		toast_vacuum_params.toast_parent = relid;
 
-		vacuum_rel(toast_relid, NULL, toast_vacuum_params, bstrategy);
+		vacuum_rel(toast_relid, NULL, toast_vacuum_params, bstrategy,
+				   isTopLevel);
 	}
 
 	/*
diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c
index 22e5164adbf..1000b7bb06e 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -14,6 +14,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
+#include "commands/cluster.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
@@ -177,6 +178,10 @@ mq_putmessage(char msgtype, const char *s, size_t len)
 				SendProcSignal(pq_mq_parallel_leader_pid,
 							   PROCSIG_PARALLEL_APPLY_MESSAGE,
 							   pq_mq_parallel_leader_proc_number);
+			else if (IsRepackWorker())
+				SendProcSignal(pq_mq_parallel_leader_pid,
+							   PROCSIG_REPACK_MESSAGE,
+							   pq_mq_parallel_leader_proc_number);
 			else
 			{
 				Assert(IsParallelWorker());
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 4f5292d8f88..5e3cfe2d6f8 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -219,5 +219,6 @@ pg_test_mod_args = pg_mod_args + {
 subdir('jit/llvm')
 subdir('replication/libpqwalreceiver')
 subdir('replication/pgoutput')
+subdir('replication/pgoutput_repack')
 subdir('snowball')
 subdir('utils/mb/conversion_procs')
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 0104a86b9ec..c9ef6bed88f 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,6 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
+#include "commands/cluster.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -143,6 +144,10 @@ static const struct
 	{
 		.fn_name = "SequenceSyncWorkerMain",
 		.fn_addr = SequenceSyncWorkerMain
+	},
+	{
+		.fn_name = "RepackWorkerMain",
+		.fn_addr = RepackWorkerMain
 	}
 };
 
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 21f03864a66..2595ff0e3a6 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -33,6 +33,7 @@
 #include "access/xlogreader.h"
 #include "access/xlogrecord.h"
 #include "catalog/pg_control.h"
+#include "commands/cluster.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
@@ -420,7 +421,8 @@ heap2_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 	{
 		case XLOG_HEAP2_MULTI_INSERT:
 			if (SnapBuildProcessChange(builder, xid, buf->origptr) &&
-				!ctx->fast_forward)
+				!ctx->fast_forward &&
+				!change_useless_for_repack(buf))
 				DecodeMultiInsert(ctx, buf);
 			break;
 		case XLOG_HEAP2_NEW_CID:
@@ -466,6 +468,15 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 	TransactionId xid = XLogRecGetXid(buf->record);
 	SnapBuild  *builder = ctx->snapshot_builder;
 
+	/*
+	 * XXX Should we return here if change_useless_for_repack() returns true,
+	 * instead of calling the function below? Unlike the fast-forward case, we
+	 * shouldn't need the base snapshot for the containing transaction until
+	 * we receive a change that belongs to the table being REPACKed. Thus it
+	 * should be fine to skip SnapBuildProcessChange(), and therefore
+	 * reorderbuffer.c can create the transaction later.
+	 */
+
 	ReorderBufferProcessXid(ctx->reorder, xid, buf->origptr);
 
 	/*
@@ -483,7 +494,8 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 	{
 		case XLOG_HEAP_INSERT:
 			if (SnapBuildProcessChange(builder, xid, buf->origptr) &&
-				!ctx->fast_forward)
+				!ctx->fast_forward &&
+				!change_useless_for_repack(buf))
 				DecodeInsert(ctx, buf);
 			break;
 
@@ -495,19 +507,22 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		case XLOG_HEAP_HOT_UPDATE:
 		case XLOG_HEAP_UPDATE:
 			if (SnapBuildProcessChange(builder, xid, buf->origptr) &&
-				!ctx->fast_forward)
+				!ctx->fast_forward &&
+				!change_useless_for_repack(buf))
 				DecodeUpdate(ctx, buf);
 			break;
 
 		case XLOG_HEAP_DELETE:
 			if (SnapBuildProcessChange(builder, xid, buf->origptr) &&
-				!ctx->fast_forward)
+				!ctx->fast_forward &&
+				!change_useless_for_repack(buf))
 				DecodeDelete(ctx, buf);
 			break;
 
 		case XLOG_HEAP_TRUNCATE:
 			if (SnapBuildProcessChange(builder, xid, buf->origptr) &&
-				!ctx->fast_forward)
+				!ctx->fast_forward &&
+				!change_useless_for_repack(buf))
 				DecodeTruncate(ctx, buf);
 			break;
 
@@ -523,7 +538,8 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 		case XLOG_HEAP_CONFIRM:
 			if (SnapBuildProcessChange(builder, xid, buf->origptr) &&
-				!ctx->fast_forward)
+				!ctx->fast_forward &&
+				!change_useless_for_repack(buf))
 				DecodeSpecConfirm(ctx, buf);
 			break;
 
@@ -1020,6 +1036,15 @@ DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 	xlrec = (xl_heap_delete *) XLogRecGetData(r);
 
+	/*
+	 * Ignore changes which are considered useless for logical decoding.
+	 * Currently such changes are created by REPACK (CONCURRENTLY) when
+	 * replays DELETE commands on the new table (which is not yet visible to
+	 * other transactions).
+	 */
+	if (xlrec->flags & XLH_DELETE_NO_LOGICAL)
+		return;
+
 	/* only interested in our database */
 	XLogRecGetBlockTag(r, 0, &target_locator, NULL, NULL);
 	if (target_locator.dbOid != ctx->slot->data.database)
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 603a2b94d05..7651b187418 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -194,7 +194,11 @@ StartupDecodingContext(List *output_plugin_options,
 
 	ctx->slot = slot;
 
-	ctx->reader = XLogReaderAllocate(wal_segment_size, NULL, xl_routine, ctx);
+	/*
+	 * TODO A separate patch for PG core, unless there's really a reason to
+	 * pass ctx for private_data (May extensions expect ctx?).
+	 */
+	ctx->reader = XLogReaderAllocate(wal_segment_size, NULL, xl_routine, NULL);
 	if (!ctx->reader)
 		ereport(ERROR,
 				(errcode(ERRCODE_OUT_OF_MEMORY),
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 37f0c6028bd..9cf499ce7c6 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -440,7 +440,7 @@ SnapBuildBuildSnapshot(SnapBuild *builder)
  * for loading in different transaction.
  */
 Snapshot
-SnapBuildInitialSnapshot(SnapBuild *builder)
+SnapBuildInitialSnapshot(SnapBuild *builder, bool repack)
 {
 	Snapshot	snap;
 	TransactionId xid;
@@ -448,7 +448,7 @@ SnapBuildInitialSnapshot(SnapBuild *builder)
 	TransactionId *newxip;
 	int			newxcnt = 0;
 
-	Assert(XactIsoLevel == XACT_REPEATABLE_READ);
+	Assert(XactIsoLevel == XACT_REPEATABLE_READ || repack);
 	Assert(builder->building_full_snapshot);
 
 	/* don't allow older snapshots */
@@ -526,6 +526,11 @@ SnapBuildInitialSnapshot(SnapBuild *builder)
 	snap->xcnt = newxcnt;
 	snap->xip = newxip;
 
+	/*
+	 * FreeSnapshot() is more appropriate for REPACK than counting references.
+	 */
+	snap->copied = repack;
+
 	return snap;
 }
 
@@ -558,7 +563,7 @@ SnapBuildExportSnapshot(SnapBuild *builder)
 	XactIsoLevel = XACT_REPEATABLE_READ;
 	XactReadOnly = true;
 
-	snap = SnapBuildInitialSnapshot(builder);
+	snap = SnapBuildInitialSnapshot(builder, false);
 
 	/*
 	 * now that we've built a plain snapshot, make it active and use the
diff --git a/src/backend/replication/pgoutput_repack/Makefile b/src/backend/replication/pgoutput_repack/Makefile
new file mode 100644
index 00000000000..4efeb713b70
--- /dev/null
+++ b/src/backend/replication/pgoutput_repack/Makefile
@@ -0,0 +1,32 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for src/backend/replication/pgoutput_repack
+#
+# IDENTIFICATION
+#    src/backend/replication/pgoutput_repack
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/replication/pgoutput_repack
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+	$(WIN32RES) \
+	pgoutput_repack.o
+PGFILEDESC = "pgoutput_repack - logical replication output plugin for REPACK command"
+NAME = pgoutput_repack
+
+all: all-shared-lib
+
+include $(top_srcdir)/src/Makefile.shlib
+
+install: all installdirs install-lib
+
+installdirs: installdirs-lib
+
+uninstall: uninstall-lib
+
+clean distclean: clean-lib
+	rm -f $(OBJS)
diff --git a/src/backend/replication/pgoutput_repack/meson.build b/src/backend/replication/pgoutput_repack/meson.build
new file mode 100644
index 00000000000..6a88c0fb08d
--- /dev/null
+++ b/src/backend/replication/pgoutput_repack/meson.build
@@ -0,0 +1,18 @@
+# Copyright (c) 2022-2026, PostgreSQL Global Development Group
+
+pgoutput_repack_sources = files(
+  'pgoutput_repack.c',
+)
+
+if host_system == 'windows'
+  pgoutput_repack_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'pgoutput_repack',
+    '--FILEDESC', 'pgoutput_repack - logical replication output plugin for REPACK command',])
+endif
+
+pgoutput_repack = shared_module('pgoutput_repack',
+  pgoutput_repack_sources,
+  kwargs: pg_mod_args,
+)
+
+backend_targets += pgoutput_repack
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
new file mode 100644
index 00000000000..d0c464a98d5
--- /dev/null
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -0,0 +1,202 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgoutput_repack.c
+ *		Logical Replication output plugin for REPACK command
+ *
+ * Copyright (c) 2012-2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  src/backend/replication/pgoutput_repack/pgoutput_repack.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heaptoast.h"
+#include "commands/cluster.h"
+#include "replication/snapbuild.h"
+#include "utils/memutils.h"
+
+PG_MODULE_MAGIC;
+
+static void plugin_startup(LogicalDecodingContext *ctx,
+						   OutputPluginOptions *opt, bool is_init);
+static void plugin_shutdown(LogicalDecodingContext *ctx);
+static void plugin_begin_txn(LogicalDecodingContext *ctx,
+							 ReorderBufferTXN *txn);
+static void plugin_commit_txn(LogicalDecodingContext *ctx,
+							  ReorderBufferTXN *txn, XLogRecPtr commit_lsn);
+static void plugin_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+						  Relation rel, ReorderBufferChange *change);
+static void store_change(LogicalDecodingContext *ctx,
+						 ConcurrentChangeKind kind, HeapTuple tuple);
+
+void
+_PG_output_plugin_init(OutputPluginCallbacks *cb)
+{
+	cb->startup_cb = plugin_startup;
+	cb->begin_cb = plugin_begin_txn;
+	cb->change_cb = plugin_change;
+	cb->commit_cb = plugin_commit_txn;
+	cb->shutdown_cb = plugin_shutdown;
+}
+
+
+/* initialize this plugin */
+static void
+plugin_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
+			   bool is_init)
+{
+	ctx->output_plugin_private = NULL;
+
+	/* Probably unnecessary, as we don't use the SQL interface ... */
+	opt->output_type = OUTPUT_PLUGIN_BINARY_OUTPUT;
+
+	if (ctx->output_plugin_options != NIL)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("This plugin does not expect any options")));
+	}
+}
+
+static void
+plugin_shutdown(LogicalDecodingContext *ctx)
+{
+}
+
+/*
+ * As we don't release the slot during processing of particular table, there's
+ * no room for SQL interface, even for debugging purposes. Therefore we need
+ * neither OutputPluginPrepareWrite() nor OutputPluginWrite() in the plugin
+ * callbacks. (Although we might want to write custom callbacks, this API
+ * seems to be unnecessarily generic for our purposes.)
+ */
+
+/* BEGIN callback */
+static void
+plugin_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
+{
+}
+
+/* COMMIT callback */
+static void
+plugin_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+				  XLogRecPtr commit_lsn)
+{
+}
+
+/*
+ * Callback for individual changed tuples
+ */
+static void
+plugin_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+			  Relation relation, ReorderBufferChange *change)
+{
+	RepackDecodingState *dstate;
+
+	dstate = (RepackDecodingState *) ctx->output_writer_private;
+
+	/* Only interested in one particular relation. */
+	if (relation->rd_id != dstate->relid)
+		return;
+
+	/* Decode entry depending on its type */
+	switch (change->action)
+	{
+		case REORDER_BUFFER_CHANGE_INSERT:
+			{
+				HeapTuple	newtuple;
+
+				newtuple = change->data.tp.newtuple;
+
+				/*
+				 * Identity checks in the main function should have made this
+				 * impossible.
+				 */
+				if (newtuple == NULL)
+					elog(ERROR, "incomplete insert info.");
+
+				store_change(ctx, CHANGE_INSERT, newtuple);
+			}
+			break;
+		case REORDER_BUFFER_CHANGE_UPDATE:
+			{
+				HeapTuple	oldtuple,
+							newtuple;
+
+				oldtuple = change->data.tp.oldtuple;
+				newtuple = change->data.tp.newtuple;
+
+				if (newtuple == NULL)
+					elog(ERROR, "incomplete update info.");
+
+				if (oldtuple != NULL)
+					store_change(ctx, CHANGE_UPDATE_OLD, oldtuple);
+
+				store_change(ctx, CHANGE_UPDATE_NEW, newtuple);
+			}
+			break;
+		case REORDER_BUFFER_CHANGE_DELETE:
+			{
+				HeapTuple	oldtuple;
+
+				oldtuple = change->data.tp.oldtuple;
+
+				if (oldtuple == NULL)
+					elog(ERROR, "incomplete delete info.");
+
+				store_change(ctx, CHANGE_DELETE, oldtuple);
+			}
+			break;
+		default:
+
+			/*
+			 * Should not come here. This includes TRUNCATE of the table being
+			 * processed. heap_decode() cannot check the file locator easily,
+			 * but we assume that TRUNCATE uses AccessExclusiveLock on the
+			 * table so it should not occur during REPACK (CONCURRENTLY).
+			 */
+			Assert(false);
+			break;
+	}
+}
+
+/* Store concurrent data change. */
+static void
+store_change(LogicalDecodingContext *ctx, ConcurrentChangeKind kind,
+			 HeapTuple tuple)
+{
+	RepackDecodingState *dstate;
+	char		kind_byte = (char) kind;
+	bool		flattened = false;
+
+	dstate = (RepackDecodingState *) ctx->output_writer_private;
+
+	/* Store the change kind. */
+	BufFileWrite(dstate->file, &kind_byte, 1);
+
+	/*
+	 * ReorderBufferCommit() stores the TOAST chunks in its private memory
+	 * context and frees them after having called apply_change().  Therefore
+	 * we need flat copy (including TOAST) that we eventually copy into the
+	 * memory context which is available to decode_concurrent_changes().
+	 */
+	if (HeapTupleHasExternal(tuple))
+	{
+		/*
+		 * toast_flatten_tuple_to_datum() might be more convenient but we
+		 * don't want the decompression it does.
+		 */
+		tuple = toast_flatten_tuple(tuple, dstate->tupdesc);
+		flattened = true;
+	}
+	/* Store the tuple size ... */
+	BufFileWrite(dstate->file, &tuple->t_len, sizeof(tuple->t_len));
+	/* ... and the tuple itself. */
+	BufFileWrite(dstate->file, tuple->t_data, tuple->t_len);
+
+	/* Free the flat copy if created above. */
+	if (flattened)
+		pfree(tuple);
+}
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 79fc192b171..4a62c9f645d 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1338,7 +1338,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		{
 			Snapshot	snap;
 
-			snap = SnapBuildInitialSnapshot(ctx->snapshot_builder);
+			snap = SnapBuildInitialSnapshot(ctx->snapshot_builder, false);
 			RestoreTransactionSnapshot(snap, MyProc);
 		}
 
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 7e017c8d53b..dd980145ced 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -19,6 +19,7 @@
 
 #include "access/parallel.h"
 #include "commands/async.h"
+#include "commands/cluster.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
@@ -700,6 +701,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
 	if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE))
 		HandleParallelApplyMessageInterrupt();
 
+	if (CheckProcSignal(PROCSIG_REPACK_MESSAGE))
+		HandleRepackMessageInterrupt();
+
 	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT))
 		HandleRecoveryConflictInterrupt();
 
diff --git a/src/backend/storage/lmgr/generate-lwlocknames.pl b/src/backend/storage/lmgr/generate-lwlocknames.pl
index b49007167b0..2e7f1054e62 100644
--- a/src/backend/storage/lmgr/generate-lwlocknames.pl
+++ b/src/backend/storage/lmgr/generate-lwlocknames.pl
@@ -162,7 +162,7 @@ while (<$lwlocklist>)
 
 die
   "$wait_event_lwlocks[$lwlock_count] defined in wait_event_names.txt but "
-  . " missing from lwlocklist.h"
+  . "missing from lwlocklist.h"
   if $lwlock_count < scalar @wait_event_lwlocks;
 
 die
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index d01a09dd0c4..9711abaf2eb 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -36,6 +36,7 @@
 #include "access/xact.h"
 #include "catalog/pg_type.h"
 #include "commands/async.h"
+#include "commands/cluster.h"
 #include "commands/event_trigger.h"
 #include "commands/explain_state.h"
 #include "commands/prepare.h"
@@ -3575,6 +3576,9 @@ ProcessInterrupts(void)
 
 	if (ParallelApplyMessagePending)
 		ProcessParallelApplyMessages();
+
+	if (RepackMessagePending)
+		ProcessRepackMessages();
 }
 
 /*
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 4aa864fe3c3..b00bd794759 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -154,6 +154,7 @@ RECOVERY_CONFLICT_SNAPSHOT	"Waiting for recovery conflict resolution for a vacuu
 RECOVERY_CONFLICT_TABLESPACE	"Waiting for recovery conflict resolution for dropping a tablespace."
 RECOVERY_END_COMMAND	"Waiting for <xref linkend="guc-recovery-end-command"/> to complete."
 RECOVERY_PAUSE	"Waiting for recovery to be resumed."
+REPACK_WORKER_EXPORT	"Waiting for decoding worker to export a new output file."
 REPLICATION_ORIGIN_DROP	"Waiting for a replication origin to become inactive so it can be dropped."
 REPLICATION_SLOT_DROP	"Waiting for a replication slot to become inactive so it can be dropped."
 RESTORE_COMMAND	"Waiting for <xref linkend="guc-restore-command"/> to complete."
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index 2e6197f5f35..e0129df3e40 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -215,7 +215,6 @@ static List *exportedSnapshots = NIL;
 /* Prototypes for local functions */
 static Snapshot CopySnapshot(Snapshot snapshot);
 static void UnregisterSnapshotNoOwner(Snapshot snapshot);
-static void FreeSnapshot(Snapshot snapshot);
 static void SnapshotResetXmin(void);
 
 /* ResourceOwner callbacks to track snapshot references */
@@ -660,7 +659,7 @@ CopySnapshot(Snapshot snapshot)
  * FreeSnapshot
  *		Free the memory associated with a snapshot.
  */
-static void
+void
 FreeSnapshot(Snapshot snapshot)
 {
 	Assert(snapshot->regd_count == 0);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 199fc64ddf5..add36423e3a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5153,8 +5153,8 @@ match_previous_words(int pattern_id,
 		 * one word, so the above test is correct.
 		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
-			COMPLETE_WITH("ANALYZE", "VERBOSE");
-		else if (TailMatches("ANALYZE", "VERBOSE"))
+			COMPLETE_WITH("ANALYZE", "CONCURRENTLY", "VERBOSE");
+		else if (TailMatches("ANALYZE", "CONCURRENTLY", "VERBOSE"))
 			COMPLETE_WITH("ON", "OFF");
 	}
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index ad993c07311..02e64a1539c 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -374,14 +374,15 @@ extern void heap_multi_insert(Relation relation, TupleTableSlot **slots,
 							  BulkInsertState bistate);
 extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid,
 							 CommandId cid, Snapshot crosscheck, bool wait,
-							 TM_FailureData *tmfd, bool changingPart);
+							 TM_FailureData *tmfd, bool changingPart,
+							 bool wal_logical);
 extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid);
 extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid);
 extern TM_Result heap_update(Relation relation, const ItemPointerData *otid,
 							 HeapTuple newtup,
 							 CommandId cid, Snapshot crosscheck, bool wait,
 							 TM_FailureData *tmfd, LockTupleMode *lockmode,
-							 TU_UpdateIndexes *update_indexes);
+							 TU_UpdateIndexes *update_indexes, bool wal_logical);
 extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
 								 CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 								 bool follow_updates,
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index ce3566ba949..f1f5495556b 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -104,6 +104,8 @@
 #define XLH_DELETE_CONTAINS_OLD_KEY				(1<<2)
 #define XLH_DELETE_IS_SUPER						(1<<3)
 #define XLH_DELETE_IS_PARTITION_MOVE			(1<<4)
+/* See heap_delete() */
+#define XLH_DELETE_NO_LOGICAL					(1<<5)
 
 /* convenience macro for checking whether any form of old tuple was logged */
 #define XLH_DELETE_CONTAINS_OLD						\
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 06084752245..b9d5e5e13d9 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -629,6 +629,7 @@ typedef struct TableAmRoutine
 											  Relation OldIndex,
 											  bool use_sort,
 											  TransactionId OldestXmin,
+											  Snapshot snapshot,
 											  TransactionId *xid_cutoff,
 											  MultiXactId *multi_cutoff,
 											  double *num_tuples,
@@ -1657,6 +1658,8 @@ table_relation_copy_data(Relation rel, const RelFileLocator *newrlocator)
  *   not needed for the relation's AM
  * - *xid_cutoff - ditto
  * - *multi_cutoff - ditto
+ * - snapshot - if != NULL, ignore data changes done by transactions that this
+ *	 (MVCC) snapshot considers still in-progress or in the future.
  *
  * Output parameters:
  * - *xid_cutoff - rel's new relfrozenxid value, may be invalid
@@ -1669,6 +1672,7 @@ table_relation_copy_for_cluster(Relation OldTable, Relation NewTable,
 								Relation OldIndex,
 								bool use_sort,
 								TransactionId OldestXmin,
+								Snapshot snapshot,
 								TransactionId *xid_cutoff,
 								MultiXactId *multi_cutoff,
 								double *num_tuples,
@@ -1677,6 +1681,7 @@ table_relation_copy_for_cluster(Relation OldTable, Relation NewTable,
 {
 	OldTable->rd_tableam->relation_copy_for_cluster(OldTable, NewTable, OldIndex,
 													use_sort, OldestXmin,
+													snapshot,
 													xid_cutoff, multi_cutoff,
 													num_tuples, tups_vacuumed,
 													tups_recently_dead);
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 28741988478..1b05d5d418b 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,10 +13,17 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
 #include "storage/lock.h"
+#include "storage/shm_mq.h"
 #include "utils/relcache.h"
+#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -25,6 +32,8 @@
 #define CLUOPT_RECHECK_ISCLUSTERED 0x04 /* recheck relation state for
 										 * indisclustered */
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
+#define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
+
 
 /* options for CLUSTER */
 typedef struct ClusterParams
@@ -33,10 +42,49 @@ typedef struct ClusterParams
 } ClusterParams;
 
 
+/*
+ * The following definitions are used by REPACK CONCURRENTLY.
+ */
+
+/*
+ * Stored as a single byte in the output file.
+ */
+typedef enum
+{
+	CHANGE_INSERT,
+	CHANGE_UPDATE_OLD,
+	CHANGE_UPDATE_NEW,
+	CHANGE_DELETE
+} ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+
+	/* Tuple descriptor of the relation being processed. */
+	TupleDesc	tupdesc;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
+
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
-						ClusterParams *params);
+						ClusterParams *params, bool isTopLevel);
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
@@ -48,8 +96,13 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool swap_toast_by_content,
 							 bool check_constraints,
 							 bool is_internal,
+							 bool reindex,
 							 TransactionId frozenXid,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
+extern bool am_decoding_for_repack(void);
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/progress.h b/src/include/commands/progress.h
index 9c40772706c..b5b01c1bb6d 100644
--- a/src/include/commands/progress.h
+++ b/src/include/commands/progress.h
@@ -86,10 +86,12 @@
 #define PROGRESS_REPACK_PHASE					1
 #define PROGRESS_REPACK_INDEX_RELID				2
 #define PROGRESS_REPACK_HEAP_TUPLES_SCANNED		3
-#define PROGRESS_REPACK_HEAP_TUPLES_WRITTEN		4
-#define PROGRESS_REPACK_TOTAL_HEAP_BLKS			5
-#define PROGRESS_REPACK_HEAP_BLKS_SCANNED		6
-#define PROGRESS_REPACK_INDEX_REBUILD_COUNT		7
+#define PROGRESS_REPACK_HEAP_TUPLES_INSERTED	4
+#define PROGRESS_REPACK_HEAP_TUPLES_UPDATED		5
+#define PROGRESS_REPACK_HEAP_TUPLES_DELETED		6
+#define PROGRESS_REPACK_TOTAL_HEAP_BLKS			7
+#define PROGRESS_REPACK_HEAP_BLKS_SCANNED		8
+#define PROGRESS_REPACK_INDEX_REBUILD_COUNT		9
 
 /*
  * Phases of repack (as advertised via PROGRESS_REPACK_PHASE).
@@ -98,9 +100,10 @@
 #define PROGRESS_REPACK_PHASE_INDEX_SCAN_HEAP	2
 #define PROGRESS_REPACK_PHASE_SORT_TUPLES		3
 #define PROGRESS_REPACK_PHASE_WRITE_NEW_HEAP	4
-#define PROGRESS_REPACK_PHASE_SWAP_REL_FILES	5
-#define PROGRESS_REPACK_PHASE_REBUILD_INDEX		6
-#define PROGRESS_REPACK_PHASE_FINAL_CLEANUP		7
+#define PROGRESS_REPACK_PHASE_CATCH_UP			5
+#define PROGRESS_REPACK_PHASE_SWAP_REL_FILES	6
+#define PROGRESS_REPACK_PHASE_REBUILD_INDEX		7
+#define PROGRESS_REPACK_PHASE_FINAL_CLEANUP		8
 
 /* Progress parameters for CREATE INDEX */
 /* 3, 4 and 5 reserved for "waitfor" metrics */
diff --git a/src/include/replication/snapbuild.h b/src/include/replication/snapbuild.h
index ccded021433..2b84f0058f0 100644
--- a/src/include/replication/snapbuild.h
+++ b/src/include/replication/snapbuild.h
@@ -72,7 +72,7 @@ extern void FreeSnapshotBuilder(SnapBuild *builder);
 
 extern void SnapBuildSnapDecRefcount(Snapshot snap);
 
-extern Snapshot SnapBuildInitialSnapshot(SnapBuild *builder);
+extern Snapshot SnapBuildInitialSnapshot(SnapBuild *builder, bool repack);
 extern const char *SnapBuildExportSnapshot(SnapBuild *builder);
 extern void SnapBuildClearExportedSnapshot(void);
 extern void SnapBuildResetExportedSnapshotState(void);
diff --git a/src/include/storage/lockdefs.h b/src/include/storage/lockdefs.h
index b73bb5618e6..3785b009808 100644
--- a/src/include/storage/lockdefs.h
+++ b/src/include/storage/lockdefs.h
@@ -36,8 +36,8 @@ typedef int LOCKMODE;
 #define AccessShareLock			1	/* SELECT */
 #define RowShareLock			2	/* SELECT FOR UPDATE/FOR SHARE */
 #define RowExclusiveLock		3	/* INSERT, UPDATE, DELETE */
-#define ShareUpdateExclusiveLock 4	/* VACUUM (non-FULL), ANALYZE, CREATE
-									 * INDEX CONCURRENTLY */
+#define ShareUpdateExclusiveLock 4	/* VACUUM (non-exclusive), ANALYZE, CREATE
+									 * INDEX CONCURRENTLY, REPACK CONCURRENTLY */
 #define ShareLock				5	/* CREATE INDEX (WITHOUT CONCURRENTLY) */
 #define ShareRowExclusiveLock	6	/* like EXCLUSIVE MODE, but allows ROW
 									 * SHARE */
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 348fba53a93..a944ee0d211 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -36,6 +36,7 @@ typedef enum
 	PROCSIG_BARRIER,			/* global barrier interrupt  */
 	PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */
 	PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */
+	PROCSIG_REPACK_MESSAGE,		/* Message from repack worker */
 	PROCSIG_RECOVERY_CONFLICT,	/* backend is blocking recovery, check
 								 * PGPROC->pendingRecoveryConflicts for the
 								 * reason */
diff --git a/src/include/utils/snapmgr.h b/src/include/utils/snapmgr.h
index b8c01a291a1..0c144370308 100644
--- a/src/include/utils/snapmgr.h
+++ b/src/include/utils/snapmgr.h
@@ -63,6 +63,8 @@ extern Snapshot GetTransactionSnapshot(void);
 extern Snapshot GetLatestSnapshot(void);
 extern void SnapshotSetCommandId(CommandId curcid);
 
+extern void FreeSnapshot(Snapshot snapshot);
+
 extern Snapshot GetCatalogSnapshot(Oid relid);
 extern Snapshot GetNonHistoricCatalogSnapshot(Oid relid);
 extern void InvalidateCatalogSnapshot(void);
diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile
index a41d781f8c9..2cd7d87c533 100644
--- a/src/test/modules/injection_points/Makefile
+++ b/src/test/modules/injection_points/Makefile
@@ -14,6 +14,8 @@ REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
 
 ISOLATION = basic \
 	    inplace \
+	    repack \
+	    repack_toast \
 	    syscache-update-pruned \
 	    heap_lock_update
 
diff --git a/src/test/modules/injection_points/expected/repack.out b/src/test/modules/injection_points/expected/repack.out
new file mode 100644
index 00000000000..b575e9052ee
--- /dev/null
+++ b/src/test/modules/injection_points/expected/repack.out
@@ -0,0 +1,113 @@
+Parsed test spec with 2 sessions
+
+starting permutation: wait_before_lock change_existing change_new change_subxact1 change_subxact2 check2 wakeup_before_lock check1
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step wait_before_lock: 
+	REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_pkey;
+ <waiting ...>
+step change_existing: 
+	UPDATE repack_test SET i=10 where i=1;
+	UPDATE repack_test SET j=20 where i=2;
+	UPDATE repack_test SET i=30 where i=3;
+	UPDATE repack_test SET i=40 where i=30;
+	DELETE FROM repack_test WHERE i=4;
+
+step change_new: 
+	INSERT INTO repack_test(i, j) VALUES (5, 5), (6, 6), (7, 7), (8, 8);
+	UPDATE repack_test SET i=50 where i=5;
+	UPDATE repack_test SET j=60 where i=6;
+	DELETE FROM repack_test WHERE i=7;
+
+step change_subxact1: 
+	BEGIN;
+	INSERT INTO repack_test(i, j) VALUES (100, 100);
+	SAVEPOINT s1;
+	UPDATE repack_test SET i=101 where i=100;
+	SAVEPOINT s2;
+	UPDATE repack_test SET i=102 where i=101;
+	COMMIT;
+
+step change_subxact2: 
+	BEGIN;
+	SAVEPOINT s1;
+	INSERT INTO repack_test(i, j) VALUES (110, 110);
+	ROLLBACK TO SAVEPOINT s1;
+	INSERT INTO repack_test(i, j) VALUES (110, 111);
+	COMMIT;
+
+step check2: 
+	INSERT INTO relfilenodes(node)
+	SELECT relfilenode FROM pg_class WHERE relname='repack_test';
+
+	SELECT i, j FROM repack_test ORDER BY i, j;
+
+	INSERT INTO data_s2(i, j)
+	SELECT i, j FROM repack_test;
+
+  i|  j
+---+---
+  2| 20
+  6| 60
+  8|  8
+ 10|  1
+ 40|  3
+ 50|  5
+102|100
+110|111
+(8 rows)
+
+step wakeup_before_lock: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step wait_before_lock: <... completed>
+step check1: 
+	INSERT INTO relfilenodes(node)
+	SELECT relfilenode FROM pg_class WHERE relname='repack_test';
+
+	SELECT count(DISTINCT node) FROM relfilenodes;
+
+	SELECT i, j FROM repack_test ORDER BY i, j;
+
+	INSERT INTO data_s1(i, j)
+	SELECT i, j FROM repack_test;
+
+	SELECT count(*)
+	FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j)
+	WHERE d1.i ISNULL OR d2.i ISNULL;
+
+count
+-----
+    2
+(1 row)
+
+  i|  j
+---+---
+  2| 20
+  6| 60
+  8|  8
+ 10|  1
+ 40|  3
+ 50|  5
+102|100
+110|111
+(8 rows)
+
+count
+-----
+    0
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/src/test/modules/injection_points/expected/repack_toast.out b/src/test/modules/injection_points/expected/repack_toast.out
new file mode 100644
index 00000000000..4f866a74e32
--- /dev/null
+++ b/src/test/modules/injection_points/expected/repack_toast.out
@@ -0,0 +1,64 @@
+Parsed test spec with 2 sessions
+
+starting permutation: wait_before_lock change check2 wakeup_before_lock check1
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step wait_before_lock: 
+	REPACK (CONCURRENTLY) repack_test;
+ <waiting ...>
+step change: 
+	UPDATE repack_test SET j=get_long_string() where i=2;
+	DELETE FROM repack_test WHERE i=3;
+	INSERT INTO repack_test(i, j) VALUES (4, get_long_string());
+
+step check2: 
+	INSERT INTO relfilenodes(node)
+	SELECT c2.relfilenode
+	FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid
+	WHERE c1.relname='repack_test';
+
+	INSERT INTO data_s2(i, j)
+	SELECT i, j FROM repack_test;
+
+step wakeup_before_lock: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step wait_before_lock: <... completed>
+step check1: 
+	INSERT INTO relfilenodes(node)
+	SELECT c2.relfilenode
+	FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid
+	WHERE c1.relname='repack_test';
+
+	SELECT count(DISTINCT node) FROM relfilenodes;
+
+	INSERT INTO data_s1(i, j)
+	SELECT i, j FROM repack_test;
+
+	SELECT count(*)
+	FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j)
+	WHERE d1.i ISNULL OR d2.i ISNULL;
+
+count
+-----
+    4
+(1 row)
+
+count
+-----
+    0
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build
index fcc85414515..a414abb924b 100644
--- a/src/test/modules/injection_points/meson.build
+++ b/src/test/modules/injection_points/meson.build
@@ -45,6 +45,8 @@ tests += {
     'specs': [
       'basic',
       'inplace',
+      'repack',
+      'repack_toast',
       'syscache-update-pruned',
       'heap_lock_update',
     ],
diff --git a/src/test/modules/injection_points/specs/repack.spec b/src/test/modules/injection_points/specs/repack.spec
new file mode 100644
index 00000000000..d727a9b056b
--- /dev/null
+++ b/src/test/modules/injection_points/specs/repack.spec
@@ -0,0 +1,142 @@
+# REPACK (CONCURRENTLY) ... USING INDEX ...;
+setup
+{
+	CREATE EXTENSION injection_points;
+
+	CREATE TABLE repack_test(i int PRIMARY KEY, j int);
+	INSERT INTO repack_test(i, j) VALUES (1, 1), (2, 2), (3, 3), (4, 4);
+
+	CREATE TABLE relfilenodes(node oid);
+
+	CREATE TABLE data_s1(i int, j int);
+	CREATE TABLE data_s2(i int, j int);
+}
+
+teardown
+{
+	DROP TABLE repack_test;
+	DROP EXTENSION injection_points;
+
+	DROP TABLE relfilenodes;
+	DROP TABLE data_s1;
+	DROP TABLE data_s2;
+}
+
+session s1
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+# Perform the initial load and wait for s2 to do some data changes.
+step wait_before_lock
+{
+	REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_pkey;
+}
+# Check the table from the perspective of s1.
+#
+# Besides the contents, we also check that relfilenode has changed.
+
+# Have each session write the contents into a table and use FULL JOIN to check
+# if the outputs are identical.
+step check1
+{
+	INSERT INTO relfilenodes(node)
+	SELECT relfilenode FROM pg_class WHERE relname='repack_test';
+
+	SELECT count(DISTINCT node) FROM relfilenodes;
+
+	SELECT i, j FROM repack_test ORDER BY i, j;
+
+	INSERT INTO data_s1(i, j)
+	SELECT i, j FROM repack_test;
+
+	SELECT count(*)
+	FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j)
+	WHERE d1.i ISNULL OR d2.i ISNULL;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+}
+
+session s2
+# Change the existing data. UPDATE changes both key and non-key columns. Also
+# update one row twice to test whether tuple version generated by this session
+# can be found.
+step change_existing
+{
+	UPDATE repack_test SET i=10 where i=1;
+	UPDATE repack_test SET j=20 where i=2;
+	UPDATE repack_test SET i=30 where i=3;
+	UPDATE repack_test SET i=40 where i=30;
+	DELETE FROM repack_test WHERE i=4;
+}
+# Insert new rows and UPDATE / DELETE some of them. Again, update both key and
+# non-key column.
+step change_new
+{
+	INSERT INTO repack_test(i, j) VALUES (5, 5), (6, 6), (7, 7), (8, 8);
+	UPDATE repack_test SET i=50 where i=5;
+	UPDATE repack_test SET j=60 where i=6;
+	DELETE FROM repack_test WHERE i=7;
+}
+
+# When applying concurrent data changes, we should see the effects of an
+# in-progress subtransaction.
+#
+# XXX Not sure this test is useful now - it was designed for the patch that
+# preserves tuple visibility and which therefore modifies
+# TransactionIdIsCurrentTransactionId().
+step change_subxact1
+{
+	BEGIN;
+	INSERT INTO repack_test(i, j) VALUES (100, 100);
+	SAVEPOINT s1;
+	UPDATE repack_test SET i=101 where i=100;
+	SAVEPOINT s2;
+	UPDATE repack_test SET i=102 where i=101;
+	COMMIT;
+}
+
+# When applying concurrent data changes, we should not see the effects of a
+# rolled back subtransaction.
+#
+# XXX Is this test useful? See above.
+step change_subxact2
+{
+	BEGIN;
+	SAVEPOINT s1;
+	INSERT INTO repack_test(i, j) VALUES (110, 110);
+	ROLLBACK TO SAVEPOINT s1;
+	INSERT INTO repack_test(i, j) VALUES (110, 111);
+	COMMIT;
+}
+
+# Check the table from the perspective of s2.
+step check2
+{
+	INSERT INTO relfilenodes(node)
+	SELECT relfilenode FROM pg_class WHERE relname='repack_test';
+
+	SELECT i, j FROM repack_test ORDER BY i, j;
+
+	INSERT INTO data_s2(i, j)
+	SELECT i, j FROM repack_test;
+}
+step wakeup_before_lock
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+# Test if data changes introduced while one session is performing REPACK
+# CONCURRENTLY find their way into the table.
+permutation
+	wait_before_lock
+	change_existing
+	change_new
+	change_subxact1
+	change_subxact2
+	check2
+	wakeup_before_lock
+	check1
diff --git a/src/test/modules/injection_points/specs/repack_toast.spec b/src/test/modules/injection_points/specs/repack_toast.spec
new file mode 100644
index 00000000000..b48abf21450
--- /dev/null
+++ b/src/test/modules/injection_points/specs/repack_toast.spec
@@ -0,0 +1,105 @@
+# REPACK (CONCURRENTLY);
+#
+# Test handling of TOAST. At the same time, no tuplesort.
+setup
+{
+	CREATE EXTENSION injection_points;
+
+	-- Return a string that needs to be TOASTed.
+	CREATE FUNCTION get_long_string()
+	RETURNS text
+	LANGUAGE sql as $$
+		SELECT string_agg(chr(65 + trunc(25 * random())::int), '')
+		FROM generate_series(1, 2048) s(x);
+	$$;
+
+	CREATE TABLE repack_test(i int PRIMARY KEY, j text);
+	INSERT INTO repack_test(i, j) VALUES (1, get_long_string()),
+		(2, get_long_string()), (3, get_long_string());
+
+	CREATE TABLE relfilenodes(node oid);
+
+	CREATE TABLE data_s1(i int, j text);
+	CREATE TABLE data_s2(i int, j text);
+}
+
+teardown
+{
+	DROP TABLE repack_test;
+	DROP EXTENSION injection_points;
+	DROP FUNCTION get_long_string();
+
+	DROP TABLE relfilenodes;
+	DROP TABLE data_s1;
+	DROP TABLE data_s2;
+}
+
+session s1
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+# Perform the initial load and wait for s2 to do some data changes.
+step wait_before_lock
+{
+	REPACK (CONCURRENTLY) repack_test;
+}
+# Check the table from the perspective of s1.
+#
+# Besides the contents, we also check that relfilenode has changed.
+
+# Have each session write the contents into a table and use FULL JOIN to check
+# if the outputs are identical.
+step check1
+{
+	INSERT INTO relfilenodes(node)
+	SELECT c2.relfilenode
+	FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid
+	WHERE c1.relname='repack_test';
+
+	SELECT count(DISTINCT node) FROM relfilenodes;
+
+	INSERT INTO data_s1(i, j)
+	SELECT i, j FROM repack_test;
+
+	SELECT count(*)
+	FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j)
+	WHERE d1.i ISNULL OR d2.i ISNULL;
+}
+teardown
+{
+    SELECT injection_points_detach('repack-concurrently-before-lock');
+}
+
+session s2
+step change
+{
+	UPDATE repack_test SET j=get_long_string() where i=2;
+	DELETE FROM repack_test WHERE i=3;
+	INSERT INTO repack_test(i, j) VALUES (4, get_long_string());
+}
+# Check the table from the perspective of s2.
+step check2
+{
+	INSERT INTO relfilenodes(node)
+	SELECT c2.relfilenode
+	FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid
+	WHERE c1.relname='repack_test';
+
+	INSERT INTO data_s2(i, j)
+	SELECT i, j FROM repack_test;
+}
+step wakeup_before_lock
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+# Test if data changes introduced while one session is performing REPACK
+# CONCURRENTLY find their way into the table.
+permutation
+	wait_before_lock
+	change
+	check2
+	wakeup_before_lock
+	check1
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index f373ad704b6..e681b823c98 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2014,7 +2014,7 @@ pg_stat_progress_cluster| SELECT pid,
     phase,
     repack_index_relid AS cluster_index_relid,
     heap_tuples_scanned,
-    heap_tuples_written,
+    (heap_tuples_inserted + heap_tuples_updated) AS heap_tuples_written,
     heap_blks_total,
     heap_blks_scanned,
     index_rebuild_count
@@ -2094,17 +2094,20 @@ pg_stat_progress_repack| SELECT s.pid,
             WHEN 2 THEN 'index scanning heap'::text
             WHEN 3 THEN 'sorting tuples'::text
             WHEN 4 THEN 'writing new heap'::text
-            WHEN 5 THEN 'swapping relation files'::text
-            WHEN 6 THEN 'rebuilding index'::text
-            WHEN 7 THEN 'performing final cleanup'::text
+            WHEN 5 THEN 'catch-up'::text
+            WHEN 6 THEN 'swapping relation files'::text
+            WHEN 7 THEN 'rebuilding index'::text
+            WHEN 8 THEN 'performing final cleanup'::text
             ELSE NULL::text
         END AS phase,
     (s.param3)::oid AS repack_index_relid,
     s.param4 AS heap_tuples_scanned,
-    s.param5 AS heap_tuples_written,
-    s.param6 AS heap_blks_total,
-    s.param7 AS heap_blks_scanned,
-    s.param8 AS index_rebuild_count
+    s.param5 AS heap_tuples_inserted,
+    s.param6 AS heap_tuples_updated,
+    s.param7 AS heap_tuples_deleted,
+    s.param8 AS heap_blks_total,
+    s.param9 AS heap_blks_scanned,
+    s.param10 AS index_rebuild_count
    FROM (pg_stat_get_progress_info('REPACK'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)));
 pg_stat_progress_vacuum| SELECT s.pid,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3da19d41413..1cea6db3559 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -421,6 +421,7 @@ CatCacheHeader
 CatalogId
 CatalogIdMapEntry
 CatalogIndexState
+ChangeDest
 ChangeVarNodes_callback
 ChangeVarNodes_context
 ChannelName
@@ -498,6 +499,7 @@ CompressFileHandle
 CompressionLocation
 CompressorState
 ComputeXidHorizonsResult
+ConcurrentChangeKind
 ConditionVariable
 ConditionVariableMinimallyPadded
 ConditionalStack
@@ -637,6 +639,8 @@ DeclareCursorStmt
 DecodedBkpBlock
 DecodedXLogRecord
 DecodingOutputState
+DecodingWorker
+DecodingWorkerShared
 DefElem
 DefElemAction
 DefaultACLInfo
@@ -1282,6 +1286,7 @@ IndexElem
 IndexFetchHeapData
 IndexFetchTableData
 IndexInfo
+IndexInsertState
 IndexList
 IndexOnlyScan
 IndexOnlyScanState
@@ -2582,6 +2587,7 @@ ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
 RepackCommand
+RepackDecodingState
 RepackStmt
 ReparameterizeForeignPathByChild_function
 ReplOriginId
-- 
2.47.3


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v41-0004-Serialize-decoded-tuples-without-flattening.patch



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


end of thread, other threads:[~2026-03-11 14:16 UTC | newest]

Thread overview: 190+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2018-10-28 15:09 [PATCH 2/2] review Tomas Vondra <tomas@2ndquadrant.com>
2018-10-28 17:38 [PATCH 2/2] review Tomas Vondra <tomas@2ndquadrant.com>
2021-01-09 02:17 [PATCH 1/2] review Tomas Vondra <tomas@2ndquadrant.com>
2021-03-09 20:09 [PATCH 2/2] review Tomas Vondra <tomas.vondra@postgresql.org>
2021-03-16 16:29 [PATCH 02/10] review Tomas Vondra <tomas.vondra@postgresql.org>
2021-03-16 18:26 [PATCH 04/10] review Tomas Vondra <tomas.vondra@postgresql.org>
2021-03-17 01:19 [PATCH 06/10] review Tomas Vondra <tomas.vondra@postgresql.org>
2021-03-17 01:36 [PATCH 08/10] review Tomas Vondra <tomas.vondra@postgresql.org>
2025-04-03 18:14 [PATCH 2/2] review Álvaro Herrera <alvherre@alvh.no-ip.org>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-02-06 09:24 [PATCH v10 2/2] review Álvaro Herrera <alvherre@kurilemu.de>
2026-03-11 14:16 [PATCH 3/5] Add CONCURRENTLY option to REPACK command. Antonin Houska <ah@cybertec.at>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox