mirror of
https://github.com/postgres/postgres.git
synced 2025-10-24 01:29:19 +03:00
Commit5d2e1cc117introduced some strsep() uses, but it did the memory management wrong in some cases. We need to keep a separate pointer to the allocate memory so that we can free it later, because strsep() advances the pointer we pass to it, and it at the end it will be NULL, so any free() calls won't do anything. (This fixes two of the four places changed in commit5d2e1cc117. The other two don't have this problem.) Reported-by: Alexander Lakhin <exclusion@gmail.com> Discussion: https://www.postgresql.org/message-id/flat/79692bf9-17d3-41e6-b9c9-fc8c3944222a@eisentraut.org
338 lines
7.0 KiB
C
338 lines
7.0 KiB
C
/*-------------------------------------------------------------------------
|
|
* Logging framework for frontend programs
|
|
*
|
|
* Copyright (c) 2018-2024, 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)
|
|
{
|
|
char *token;
|
|
char *cp = colors;
|
|
|
|
while ((token = strsep(&cp, ":")))
|
|
{
|
|
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);
|
|
}
|