From 4fb00d2aa6de4c27fe320823324086a67a390ab0 Mon Sep 17 00:00:00 2001 From: Euler Taveira Date: Mon, 11 Dec 2023 17:24:50 -0300 Subject: [PATCH] log_min_messages per backend type Change log_min_messages from a single element to a comma-separated list of elements. Each element is backendtype:loglevel. The backendtype are archiver, autovacuum (includes launcher and workers), backend, bgworker, bgwriter, checkpointer, logger, slotsyncworker, walreceiver, walsender, walsummarizer and walwriter. It should be part of this list a single log level that is applied as a final step to the backend types that are not informed. The old syntax (a single log level) is still accepted for backward compatibility. In this case, it means the informed log level is applied for the backend types that are not informed. The default log level is the same (WARNING) for all backend types. --- doc/src/sgml/config.sgml | 30 ++- src/backend/commands/extension.c | 2 +- src/backend/commands/variable.c | 187 ++++++++++++++++++ src/backend/utils/error/elog.c | 2 +- src/backend/utils/misc/guc_tables.c | 81 ++++++-- src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/miscadmin.h | 2 + src/include/utils/guc.h | 2 +- src/include/utils/guc_hooks.h | 2 + src/include/utils/guc_tables.h | 4 + src/test/regress/expected/guc.out | 48 +++++ src/test/regress/sql/guc.sql | 16 ++ 12 files changed, 352 insertions(+), 25 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index a8542fe41cec..d50cf22365c6 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -6962,7 +6962,7 @@ local0.* /var/log/postgresql - log_min_messages (enum) + log_min_messages (string) log_min_messages configuration parameter @@ -6971,14 +6971,26 @@ local0.* /var/log/postgresql Controls which message levels are written to the server log. - Valid values are DEBUG5, DEBUG4, - DEBUG3, DEBUG2, DEBUG1, - INFO, NOTICE, WARNING, - ERROR, LOG, FATAL, and - PANIC. Each level includes all the levels that - follow it. The later the level, the fewer messages are sent - to the log. The default is WARNING. Note that - LOG has a different rank here than in + Valid values are a comma-separated list of backendtype:level + and a single level. The list allows it to use + different levels per backend type. + Valid backendtype values are archiver, + autovacuum, backend, + bgworker, bgwriter, + checkpointer, logger, + slotsyncworker, walreceiver, + walsender, walsummarizer, and + walwriter. + Valid LEVEL values are DEBUG5, + DEBUG4, DEBUG3, DEBUG2, + DEBUG1, INFO, NOTICE, + WARNING, ERROR, LOG, + FATAL, and PANIC. Each level includes + all the levels that follow it. The later the level, the fewer messages are sent + to the log. The single LEVEL is mandatory and it is + assigned to the backend types that are not specified in the list. The + default is WARNING for all backend types. + Note that LOG has a different rank here than in . Only superusers and users with the appropriate SET privilege can change this setting. diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 180f4af9be36..86f9e77bb0e4 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control, (void) set_config_option("client_min_messages", "warning", PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); - if (log_min_messages < WARNING) + if (log_min_messages[MyBackendType] < WARNING) (void) set_config_option_ext("log_min_messages", "warning", PGC_SUSET, PGC_S_SESSION, BOOTSTRAP_SUPERUSERID, diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c index a9f2a3a30624..59b17be5c023 100644 --- a/src/backend/commands/variable.c +++ b/src/backend/commands/variable.c @@ -35,6 +35,7 @@ #include "utils/datetime.h" #include "utils/fmgrprotos.h" #include "utils/guc_hooks.h" +#include "utils/guc_tables.h" #include "utils/snapmgr.h" #include "utils/syscache.h" #include "utils/timestamp.h" @@ -1259,3 +1260,189 @@ check_ssl(bool *newval, void **extra, GucSource source) #endif return true; } + +/* + * GUC check_hook for log_min_messages + * + * The parsing consists of a comma-separated list of BACKENDTYPENAME:LEVEL + * elements. BACKENDTYPENAME is log_min_messages_backend_types. LEVEL is + * server_message_level_options. A single LEVEL element should be part of this + * list and it is applied as a final step to the backend types that are not + * specified. For backward compatibility, the old syntax is still accepted and + * it means to apply this level for all backend types. + */ +bool +check_log_min_messages(char **newval, void **extra, GucSource source) +{ + char *rawstring; + List *elemlist; + ListCell *l; + int newlevels[BACKEND_NUM_TYPES]; + bool assigned[BACKEND_NUM_TYPES]; + int genericlevel = -1; /* -1 means not assigned */ + + /* Initialize the array. */ + memset(newlevels, WARNING, BACKEND_NUM_TYPES * sizeof(int)); + memset(assigned, false, BACKEND_NUM_TYPES * sizeof(bool)); + + /* Need a modifiable copy of string. */ + rawstring = pstrdup(*newval); + + /* Parse string into list of identifiers. */ + if (!SplitGUCList(rawstring, ',', &elemlist)) + { + /* syntax error in list */ + GUC_check_errdetail("List syntax is invalid."); + pfree(rawstring); + list_free(elemlist); + return false; + } + + /* Validate and assign log level and backend type. */ + foreach(l, elemlist) + { + char *tok = (char *) lfirst(l); + char *sep; + const struct config_enum_entry *entry; + + /* + * Check whether there is a backend type following the log level. If + * there is no separator, it means this log level should be applied + * for all backend types (backward compatibility). + */ + sep = strchr(tok, ':'); + if (sep == NULL) + { + bool found = false; + + /* Reject duplicates for generic log level. */ + if (genericlevel != -1) + { + GUC_check_errdetail("Generic log level was already assigned."); + pfree(rawstring); + list_free(elemlist); + return false; + } + + /* Is the log level valid? */ + for (entry = server_message_level_options; entry && entry->name; entry++) + { + if (pg_strcasecmp(entry->name, tok) == 0) + { + genericlevel = entry->val; + found = true; + break; + } + } + + if (!found) + { + GUC_check_errdetail("Unrecognized log level: \"%s\".", tok); + pfree(rawstring); + list_free(elemlist); + return false; + } + } + else + { + char *loglevel; + char *btype; + bool found = false; + + btype = palloc((sep - tok) + 1); + memcpy(btype, tok, sep - tok); + btype[sep - tok] = '\0'; + loglevel = pstrdup(sep + 1); + + /* Is the log level valid? */ + for (entry = server_message_level_options; entry && entry->name; entry++) + { + if (pg_strcasecmp(entry->name, loglevel) == 0) + { + found = true; + break; + } + } + + if (!found) + { + GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel); + pfree(rawstring); + list_free(elemlist); + return false; + } + + /* + * Is the backend type name valid? There might be multiple entries + * per backend type, don't bail out when find first occurrence. + */ + found = false; + for (int i = 0; i < BACKEND_NUM_TYPES; i++) + { + if (pg_strcasecmp(log_min_messages_backend_types[i], btype) == 0) + { + newlevels[i] = entry->val; + assigned[i] = true; + found = true; + } + } + + if (!found) + { + GUC_check_errdetail("Unrecognized backend type: \"%s\".", btype); + pfree(rawstring); + list_free(elemlist); + return false; + } + } + } + + /* + * Generic log level must be specified. It is a good idea to specify a + * generic log level to make it clear that it is the fallback value. + * Although, we can document it, it might confuse users that used to + * specify a single log level in prior releases. + */ + if (genericlevel == -1) + { + GUC_check_errdetail("Generic log level was not defined."); + pfree(rawstring); + list_free(elemlist); + return false; + } + + /* + * Apply the generic log level (the one without a backend type) after all + * of the specific backend type have been assigned. Hence, it doesn't + * matter the order you specify the generic log level, the final result + * will be the same. + */ + for (int i = 0; i < BACKEND_NUM_TYPES; i++) + { + if (!assigned[i]) + newlevels[i] = genericlevel; + } + + pfree(rawstring); + list_free(elemlist); + + /* + * Pass back data for assign_log_min_messages to use. + */ + *extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int)); + if (!*extra) + return false; + memcpy(*extra, newlevels, BACKEND_NUM_TYPES * sizeof(int)); + + return true; +} + +/* + * GUC assign_hook for log_min_messages + */ +void +assign_log_min_messages(const char *newval, void *extra) +{ + for (int i = 0; i < BACKEND_NUM_TYPES; i++) + log_min_messages[i] = ((int *) extra)[i]; +} diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index d6299633ab79..11a9b554e97d 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level) static inline bool should_output_to_server(int elevel) { - return is_log_level_output(elevel, log_min_messages); + return is_log_level_output(elevel, log_min_messages[MyBackendType]); } /* diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index d54df555fba9..54b2add96f2c 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -142,7 +142,7 @@ static const struct config_enum_entry client_message_level_options[] = { {NULL, 0, false} }; -static const struct config_enum_entry server_message_level_options[] = { +const struct config_enum_entry server_message_level_options[] = { {"debug5", DEBUG5, false}, {"debug4", DEBUG4, false}, {"debug3", DEBUG3, false}, @@ -524,7 +524,6 @@ static bool default_with_oids = false; bool current_role_is_superuser; int log_min_error_statement = ERROR; -int log_min_messages = WARNING; int client_min_messages = NOTICE; int log_min_duration_sample = -1; int log_min_duration_statement = -1; @@ -582,6 +581,7 @@ static char *server_version_string; static int server_version_num; static char *debug_io_direct_string; static char *restrict_nonsystem_relation_kind_string; +static char *log_min_messages_string; #ifdef HAVE_SYSLOG #define DEFAULT_SYSLOG_FACILITY LOG_LOCAL0 @@ -626,6 +626,60 @@ char *role_string; /* should be static, but guc.c needs to get at this */ bool in_hot_standby_guc; +/* + * This must match enum BackendType! It should be static, but + * commands/variable.c needs to get at this. + */ +int log_min_messages[] = { + [B_INVALID] = WARNING, + [B_BACKEND] = WARNING, + [B_DEAD_END_BACKEND] = WARNING, + [B_AUTOVAC_LAUNCHER] = WARNING, + [B_AUTOVAC_WORKER] = WARNING, + [B_BG_WORKER] = WARNING, + [B_WAL_SENDER] = WARNING, + [B_SLOTSYNC_WORKER] = WARNING, + [B_STANDALONE_BACKEND] = WARNING, + [B_ARCHIVER] = WARNING, + [B_BG_WRITER] = WARNING, + [B_CHECKPOINTER] = WARNING, + [B_STARTUP] = WARNING, + [B_WAL_RECEIVER] = WARNING, + [B_WAL_SUMMARIZER] = WARNING, + [B_WAL_WRITER] = WARNING, + [B_LOGGER] = WARNING, +}; + +StaticAssertDecl(lengthof(log_min_messages) == BACKEND_NUM_TYPES, + "array length mismatch"); + +/* + * This must match enum BackendType! It might be in commands/variable.c but for + * convenience it is near log_min_messages. + */ +const char *const log_min_messages_backend_types[] = { + [B_INVALID] = "backend", /* XXX same as backend? */ + [B_BACKEND] = "backend", + [B_DEAD_END_BACKEND] = "backend", /* XXX same as backend? */ + [B_AUTOVAC_LAUNCHER] = "autovacuum", + [B_AUTOVAC_WORKER] = "autovacuum", + [B_BG_WORKER] = "bgworker", + [B_WAL_SENDER] = "walsender", + [B_SLOTSYNC_WORKER] = "slotsyncworker", + [B_STANDALONE_BACKEND] = "backend", /* XXX same as backend? */ + [B_ARCHIVER] = "archiver", + [B_BG_WRITER] = "bgwriter", + [B_CHECKPOINTER] = "checkpointer", + [B_STARTUP] = "backend", /* XXX same as backend? */ + [B_WAL_RECEIVER] = "walreceiver", + [B_WAL_SUMMARIZER] = "walsummarizer", + [B_WAL_WRITER] = "walwriter", + [B_LOGGER] = "logger", +}; + +StaticAssertDecl(lengthof(log_min_messages_backend_types) == BACKEND_NUM_TYPES, + "array length mismatch"); + /* * Displayable names for context types (enum GucContext) @@ -4286,6 +4340,18 @@ struct config_string ConfigureNamesString[] = check_client_encoding, assign_client_encoding, NULL }, + { + {"log_min_messages", PGC_SUSET, LOGGING_WHEN, + gettext_noop("Sets the message levels that are logged."), + gettext_noop("Each level includes all the levels that follow it. The later" + " the level, the fewer messages are sent."), + GUC_LIST_INPUT + }, + &log_min_messages_string, + "WARNING", + check_log_min_messages, assign_log_min_messages, NULL + }, + { {"log_line_prefix", PGC_SIGHUP, LOGGING_WHAT, gettext_noop("Controls information prefixed to each log line."), @@ -5098,17 +5164,6 @@ struct config_enum ConfigureNamesEnum[] = NULL, NULL, NULL }, - { - {"log_min_messages", PGC_SUSET, LOGGING_WHEN, - gettext_noop("Sets the message levels that are logged."), - gettext_noop("Each level includes all the levels that follow it. The later" - " the level, the fewer messages are sent.") - }, - &log_min_messages, - WARNING, server_message_level_options, - NULL, NULL, NULL - }, - { {"log_min_error_statement", PGC_SUSET, LOGGING_WHEN, gettext_noop("Causes all statements generating error at or above this level to be logged."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 25fe90a430f4..e8721ecc143e 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -539,6 +539,7 @@ # log # fatal # panic + # or a comma-separated list of backend_type:level #log_min_error_statement = error # values in order of decreasing detail: # debug5 diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 0d8528b28751..daf59c4e8221 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -332,6 +332,8 @@ extern void SwitchBackToLocalLatch(void); * * If you add entries, please also update the child_process_kinds array in * launch_backend.c. + * XXX If you add a new backend type or change the order, update + * log_min_messages because it relies on this order to work correctly. */ typedef enum BackendType { diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index f619100467df..e10e24940a74 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -271,7 +271,7 @@ extern PGDLLIMPORT bool log_duration; extern PGDLLIMPORT int log_parameter_max_length; extern PGDLLIMPORT int log_parameter_max_length_on_error; extern PGDLLIMPORT int log_min_error_statement; -extern PGDLLIMPORT int log_min_messages; +extern PGDLLIMPORT int log_min_messages[]; extern PGDLLIMPORT int client_min_messages; extern PGDLLIMPORT int log_min_duration_sample; extern PGDLLIMPORT int log_min_duration_statement; diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 799fa7ace684..a0b2d13ec4f1 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -178,5 +178,7 @@ extern bool check_synchronized_standby_slots(char **newval, void **extra, extern void assign_synchronized_standby_slots(const char *newval, void *extra); extern bool check_idle_replication_slot_timeout(int *newval, void **extra, GucSource source); +extern bool check_log_min_messages(char **newval, void **extra, GucSource source); +extern void assign_log_min_messages(const char *newval, void *extra); #endif /* GUC_HOOKS_H */ diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h index f72ce944d7f9..2aefc653f209 100644 --- a/src/include/utils/guc_tables.h +++ b/src/include/utils/guc_tables.h @@ -298,6 +298,10 @@ struct config_enum void *reset_extra; }; +/* log_min_messages */ +extern PGDLLIMPORT const char *const log_min_messages_backend_types[]; +extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[]; + /* constant tables corresponding to enums above and in guc.h */ extern PGDLLIMPORT const char *const config_group_names[]; extern PGDLLIMPORT const char *const config_type_names[]; diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out index 7f9e29c765cf..7572e0addbc8 100644 --- a/src/test/regress/expected/guc.out +++ b/src/test/regress/expected/guc.out @@ -913,3 +913,51 @@ SELECT name FROM tab_settings_flags (0 rows) DROP TABLE tab_settings_flags; +-- Test log_min_messages +SET log_min_messages TO fatal; +SHOW log_min_messages; + log_min_messages +------------------ + fatal +(1 row) + +SET log_min_messages TO 'fatal'; +SHOW log_min_messages; + log_min_messages +------------------ + fatal +(1 row) + +SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1'; --fail +ERROR: invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1" +DETAIL: Generic log level was not defined. +SET log_min_messages TO 'debug1, backend:error, fatal'; -- fail +ERROR: invalid value for parameter "log_min_messages": "debug1, backend:error, fatal" +DETAIL: Generic log level was already assigned. +SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1'; -- fail +ERROR: invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1" +DETAIL: Unrecognized backend type: "foo". +SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1'; -- fail +ERROR: invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1" +DETAIL: Unrecognized log level: "bar". +SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3'; +SHOW log_min_messages; + log_min_messages +------------------------------------------------------------------------------------------------- + backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3 +(1 row) + +SET log_min_messages TO 'warning, autovacuum:debug1'; +SHOW log_min_messages; + log_min_messages +---------------------------- + warning, autovacuum:debug1 +(1 row) + +SET log_min_messages TO 'autovacuum:debug1, warning'; +SHOW log_min_messages; + log_min_messages +---------------------------- + autovacuum:debug1, warning +(1 row) + diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql index f65f84a26320..e9dddb4e72d2 100644 --- a/src/test/regress/sql/guc.sql +++ b/src/test/regress/sql/guc.sql @@ -368,3 +368,19 @@ SELECT name FROM tab_settings_flags WHERE no_reset AND NOT no_reset_all ORDER BY 1; DROP TABLE tab_settings_flags; + +-- Test log_min_messages +SET log_min_messages TO fatal; +SHOW log_min_messages; +SET log_min_messages TO 'fatal'; +SHOW log_min_messages; +SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1'; --fail +SET log_min_messages TO 'debug1, backend:error, fatal'; -- fail +SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1'; -- fail +SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1'; -- fail +SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3'; +SHOW log_min_messages; +SET log_min_messages TO 'warning, autovacuum:debug1'; +SHOW log_min_messages; +SET log_min_messages TO 'autovacuum:debug1, warning'; +SHOW log_min_messages;