mirror of
https://github.com/postgres/postgres.git
synced 2025-05-01 01:04:50 +03:00
309 lines
6.3 KiB
C
309 lines
6.3 KiB
C
/*-------------------------------------------------------------------------
|
|
* Logging framework for frontend programs
|
|
*
|
|
* Copyright (c) 2018-2021, 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_locus = NULL;
|
|
|
|
#define SGR_ERROR_DEFAULT "01;31"
|
|
#define SGR_WARNING_DEFAULT "01;35"
|
|
#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, "locus") == 0)
|
|
sgr_locus = strdup(value);
|
|
}
|
|
}
|
|
|
|
free(colors);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sgr_error = SGR_ERROR_DEFAULT;
|
|
sgr_warning = SGR_WARNING_DEFAULT;
|
|
sgr_locus = SGR_LOCUS_DEFAULT;
|
|
}
|
|
}
|
|
}
|
|
|
|
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, const char *pg_restrict fmt,...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
pg_log_generic_v(level, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void
|
|
pg_log_generic_v(enum pg_log_level level, 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');
|
|
|
|
/*
|
|
* 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 (level)
|
|
{
|
|
case PG_LOG_FATAL:
|
|
if (sgr_error)
|
|
fprintf(stderr, ANSI_ESCAPE_FMT, sgr_error);
|
|
fprintf(stderr, _("fatal: "));
|
|
if (sgr_error)
|
|
fprintf(stderr, ANSI_ESCAPE_RESET);
|
|
break;
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|