summaryrefslogtreecommitdiffstats
path: root/src/common/logging.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/logging.c')
-rw-r--r--src/common/logging.c334
1 files changed, 334 insertions, 0 deletions
diff --git a/src/common/logging.c b/src/common/logging.c
new file mode 100644
index 0000000..64604c5
--- /dev/null
+++ b/src/common/logging.c
@@ -0,0 +1,334 @@
+/*-------------------------------------------------------------------------
+ * Logging framework for frontend programs
+ *
+ * Copyright (c) 2018-2022, PostgreSQL Global Development Group
+ *
+ * src/common/logging.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#error "This file is not expected to be compiled for backend code"
+#endif
+
+#include "postgres_fe.h"
+
+#include <unistd.h>
+
+#include "common/logging.h"
+
+enum pg_log_level __pg_log_level;
+
+static const char *progname;
+static int log_flags;
+
+static void (*log_pre_callback) (void);
+static void (*log_locus_callback) (const char **, uint64 *);
+
+static const char *sgr_error = NULL;
+static const char *sgr_warning = NULL;
+static const char *sgr_note = NULL;
+static const char *sgr_locus = NULL;
+
+#define SGR_ERROR_DEFAULT "01;31"
+#define SGR_WARNING_DEFAULT "01;35"
+#define SGR_NOTE_DEFAULT "01;36"
+#define SGR_LOCUS_DEFAULT "01"
+
+#define ANSI_ESCAPE_FMT "\x1b[%sm"
+#define ANSI_ESCAPE_RESET "\x1b[0m"
+
+#ifdef WIN32
+
+#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
+#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
+#endif
+
+/*
+ * Attempt to enable VT100 sequence processing for colorization on Windows.
+ * If current environment is not VT100-compatible or if this mode could not
+ * be enabled, return false.
+ */
+static bool
+enable_vt_processing(void)
+{
+ /* Check stderr */
+ HANDLE hOut = GetStdHandle(STD_ERROR_HANDLE);
+ DWORD dwMode = 0;
+
+ if (hOut == INVALID_HANDLE_VALUE)
+ return false;
+
+ /*
+ * Look for the current console settings and check if VT100 is already
+ * enabled.
+ */
+ if (!GetConsoleMode(hOut, &dwMode))
+ return false;
+ if ((dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0)
+ return true;
+
+ dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+ if (!SetConsoleMode(hOut, dwMode))
+ return false;
+ return true;
+}
+#endif /* WIN32 */
+
+/*
+ * This should be called before any output happens.
+ */
+void
+pg_logging_init(const char *argv0)
+{
+ const char *pg_color_env = getenv("PG_COLOR");
+ bool log_color = false;
+ bool color_terminal = isatty(fileno(stderr));
+
+#ifdef WIN32
+
+ /*
+ * On Windows, check if environment is VT100-compatible if using a
+ * terminal.
+ */
+ if (color_terminal)
+ color_terminal = enable_vt_processing();
+#endif
+
+ /* usually the default, but not on Windows */
+ setvbuf(stderr, NULL, _IONBF, 0);
+
+ progname = get_progname(argv0);
+ __pg_log_level = PG_LOG_INFO;
+
+ if (pg_color_env)
+ {
+ if (strcmp(pg_color_env, "always") == 0 ||
+ (strcmp(pg_color_env, "auto") == 0 && color_terminal))
+ log_color = true;
+ }
+
+ if (log_color)
+ {
+ const char *pg_colors_env = getenv("PG_COLORS");
+
+ if (pg_colors_env)
+ {
+ char *colors = strdup(pg_colors_env);
+
+ if (colors)
+ {
+ for (char *token = strtok(colors, ":"); token; token = strtok(NULL, ":"))
+ {
+ char *e = strchr(token, '=');
+
+ if (e)
+ {
+ char *name;
+ char *value;
+
+ *e = '\0';
+ name = token;
+ value = e + 1;
+
+ if (strcmp(name, "error") == 0)
+ sgr_error = strdup(value);
+ if (strcmp(name, "warning") == 0)
+ sgr_warning = strdup(value);
+ if (strcmp(name, "note") == 0)
+ sgr_note = strdup(value);
+ if (strcmp(name, "locus") == 0)
+ sgr_locus = strdup(value);
+ }
+ }
+
+ free(colors);
+ }
+ }
+ else
+ {
+ sgr_error = SGR_ERROR_DEFAULT;
+ sgr_warning = SGR_WARNING_DEFAULT;
+ sgr_note = SGR_NOTE_DEFAULT;
+ sgr_locus = SGR_LOCUS_DEFAULT;
+ }
+ }
+}
+
+/*
+ * Change the logging flags.
+ */
+void
+pg_logging_config(int new_flags)
+{
+ log_flags = new_flags;
+}
+
+/*
+ * pg_logging_init sets the default log level to INFO. Programs that prefer
+ * a different default should use this to set it, immediately afterward.
+ */
+void
+pg_logging_set_level(enum pg_log_level new_level)
+{
+ __pg_log_level = new_level;
+}
+
+/*
+ * Command line switches such as --verbose should invoke this.
+ */
+void
+pg_logging_increase_verbosity(void)
+{
+ /*
+ * The enum values are chosen such that we have to decrease __pg_log_level
+ * in order to become more verbose.
+ */
+ if (__pg_log_level > PG_LOG_NOTSET + 1)
+ __pg_log_level--;
+}
+
+void
+pg_logging_set_pre_callback(void (*cb) (void))
+{
+ log_pre_callback = cb;
+}
+
+void
+pg_logging_set_locus_callback(void (*cb) (const char **filename, uint64 *lineno))
+{
+ log_locus_callback = cb;
+}
+
+void
+pg_log_generic(enum pg_log_level level, enum pg_log_part part,
+ const char *pg_restrict fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ pg_log_generic_v(level, part, fmt, ap);
+ va_end(ap);
+}
+
+void
+pg_log_generic_v(enum pg_log_level level, enum pg_log_part part,
+ const char *pg_restrict fmt, va_list ap)
+{
+ int save_errno = errno;
+ const char *filename = NULL;
+ uint64 lineno = 0;
+ va_list ap2;
+ size_t required_len;
+ char *buf;
+
+ Assert(progname);
+ Assert(level);
+ Assert(fmt);
+ Assert(fmt[strlen(fmt) - 1] != '\n');
+
+ /* Do nothing if log level is too low. */
+ if (level < __pg_log_level)
+ return;
+
+ /*
+ * Flush stdout before output to stderr, to ensure sync even when stdout
+ * is buffered.
+ */
+ fflush(stdout);
+
+ if (log_pre_callback)
+ log_pre_callback();
+
+ if (log_locus_callback)
+ log_locus_callback(&filename, &lineno);
+
+ fmt = _(fmt);
+
+ if (!(log_flags & PG_LOG_FLAG_TERSE) || filename)
+ {
+ if (sgr_locus)
+ fprintf(stderr, ANSI_ESCAPE_FMT, sgr_locus);
+ if (!(log_flags & PG_LOG_FLAG_TERSE))
+ fprintf(stderr, "%s:", progname);
+ if (filename)
+ {
+ fprintf(stderr, "%s:", filename);
+ if (lineno > 0)
+ fprintf(stderr, UINT64_FORMAT ":", lineno);
+ }
+ fprintf(stderr, " ");
+ if (sgr_locus)
+ fprintf(stderr, ANSI_ESCAPE_RESET);
+ }
+
+ if (!(log_flags & PG_LOG_FLAG_TERSE))
+ {
+ switch (part)
+ {
+ case PG_LOG_PRIMARY:
+ switch (level)
+ {
+ case PG_LOG_ERROR:
+ if (sgr_error)
+ fprintf(stderr, ANSI_ESCAPE_FMT, sgr_error);
+ fprintf(stderr, _("error: "));
+ if (sgr_error)
+ fprintf(stderr, ANSI_ESCAPE_RESET);
+ break;
+ case PG_LOG_WARNING:
+ if (sgr_warning)
+ fprintf(stderr, ANSI_ESCAPE_FMT, sgr_warning);
+ fprintf(stderr, _("warning: "));
+ if (sgr_warning)
+ fprintf(stderr, ANSI_ESCAPE_RESET);
+ break;
+ default:
+ break;
+ }
+ break;
+ case PG_LOG_DETAIL:
+ if (sgr_note)
+ fprintf(stderr, ANSI_ESCAPE_FMT, sgr_note);
+ fprintf(stderr, _("detail: "));
+ if (sgr_note)
+ fprintf(stderr, ANSI_ESCAPE_RESET);
+ break;
+ case PG_LOG_HINT:
+ if (sgr_note)
+ fprintf(stderr, ANSI_ESCAPE_FMT, sgr_note);
+ fprintf(stderr, _("hint: "));
+ if (sgr_note)
+ fprintf(stderr, ANSI_ESCAPE_RESET);
+ break;
+ }
+ }
+
+ errno = save_errno;
+
+ va_copy(ap2, ap);
+ required_len = vsnprintf(NULL, 0, fmt, ap2) + 1;
+ va_end(ap2);
+
+ buf = pg_malloc_extended(required_len, MCXT_ALLOC_NO_OOM);
+
+ errno = save_errno; /* malloc might change errno */
+
+ if (!buf)
+ {
+ /* memory trouble, just print what we can and get out of here */
+ vfprintf(stderr, fmt, ap);
+ return;
+ }
+
+ vsnprintf(buf, required_len, fmt, ap);
+
+ /* strip one newline, for PQerrorMessage() */
+ if (required_len >= 2 && buf[required_len - 2] == '\n')
+ buf[required_len - 2] = '\0';
+
+ fprintf(stderr, "%s\n", buf);
+
+ free(buf);
+}