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;