Age Owner TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : * Logging framework for frontend programs
3 : *
4 : * Copyright (c) 2018-2023, PostgreSQL Global Development Group
5 : *
6 : * src/common/logging.c
7 : *
8 : *-------------------------------------------------------------------------
9 : */
10 :
11 : #ifndef FRONTEND
12 : #error "This file is not expected to be compiled for backend code"
13 : #endif
14 :
15 : #include "postgres_fe.h"
16 :
17 : #include <unistd.h>
18 :
19 : #include "common/logging.h"
20 :
21 : enum pg_log_level __pg_log_level;
22 :
23 : static const char *progname;
24 : static int log_flags;
25 :
26 : static void (*log_pre_callback) (void);
27 : static void (*log_locus_callback) (const char **, uint64 *);
28 :
29 : static const char *sgr_error = NULL;
30 : static const char *sgr_warning = NULL;
31 : static const char *sgr_note = NULL;
32 : static const char *sgr_locus = NULL;
33 :
34 : #define SGR_ERROR_DEFAULT "01;31"
35 : #define SGR_WARNING_DEFAULT "01;35"
36 : #define SGR_NOTE_DEFAULT "01;36"
37 : #define SGR_LOCUS_DEFAULT "01"
38 :
39 : #define ANSI_ESCAPE_FMT "\x1b[%sm"
40 : #define ANSI_ESCAPE_RESET "\x1b[0m"
41 :
42 : #ifdef WIN32
43 :
44 : #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
45 : #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
46 : #endif
47 :
48 : /*
49 : * Attempt to enable VT100 sequence processing for colorization on Windows.
50 : * If current environment is not VT100-compatible or if this mode could not
51 : * be enabled, return false.
52 : */
53 : static bool
54 : enable_vt_processing(void)
55 : {
56 : /* Check stderr */
57 : HANDLE hOut = GetStdHandle(STD_ERROR_HANDLE);
58 : DWORD dwMode = 0;
59 :
60 : if (hOut == INVALID_HANDLE_VALUE)
61 : return false;
62 :
63 : /*
64 : * Look for the current console settings and check if VT100 is already
65 : * enabled.
66 : */
67 : if (!GetConsoleMode(hOut, &dwMode))
68 : return false;
69 : if ((dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0)
70 : return true;
71 :
72 : dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
73 : if (!SetConsoleMode(hOut, dwMode))
74 : return false;
75 : return true;
76 : }
77 : #endif /* WIN32 */
78 :
79 : /*
80 : * This should be called before any output happens.
81 : */
82 : void
1469 peter 83 CBC 9526 : pg_logging_init(const char *argv0)
84 : {
85 9526 : const char *pg_color_env = getenv("PG_COLOR");
86 9526 : bool log_color = false;
1133 michael 87 9526 : bool color_terminal = isatty(fileno(stderr));
88 :
89 : #ifdef WIN32
90 :
91 : /*
92 : * On Windows, check if environment is VT100-compatible if using a
93 : * terminal.
94 : */
95 : if (color_terminal)
96 : color_terminal = enable_vt_processing();
97 : #endif
98 :
99 : /* usually the default, but not on Windows */
1469 peter 100 9526 : setvbuf(stderr, NULL, _IONBF, 0);
101 :
102 9526 : progname = get_progname(argv0);
103 9526 : __pg_log_level = PG_LOG_INFO;
104 :
105 9526 : if (pg_color_env)
106 : {
1469 peter 107 UBC 0 : if (strcmp(pg_color_env, "always") == 0 ||
1133 michael 108 0 : (strcmp(pg_color_env, "auto") == 0 && color_terminal))
1469 peter 109 0 : log_color = true;
110 : }
111 :
1469 peter 112 CBC 9526 : if (log_color)
113 : {
1469 peter 114 UBC 0 : const char *pg_colors_env = getenv("PG_COLORS");
115 :
116 0 : if (pg_colors_env)
117 : {
1418 tgl 118 0 : char *colors = strdup(pg_colors_env);
119 :
1469 peter 120 0 : if (colors)
121 : {
122 0 : for (char *token = strtok(colors, ":"); token; token = strtok(NULL, ":"))
123 : {
1418 tgl 124 0 : char *e = strchr(token, '=');
125 :
1469 peter 126 0 : if (e)
127 : {
128 : char *name;
129 : char *value;
130 :
131 0 : *e = '\0';
132 0 : name = token;
133 0 : value = e + 1;
134 :
135 0 : if (strcmp(name, "error") == 0)
136 0 : sgr_error = strdup(value);
137 0 : if (strcmp(name, "warning") == 0)
138 0 : sgr_warning = strdup(value);
363 139 0 : if (strcmp(name, "note") == 0)
140 0 : sgr_note = strdup(value);
1469 141 0 : if (strcmp(name, "locus") == 0)
142 0 : sgr_locus = strdup(value);
143 : }
144 : }
145 :
146 0 : free(colors);
147 : }
148 : }
149 : else
150 : {
151 0 : sgr_error = SGR_ERROR_DEFAULT;
152 0 : sgr_warning = SGR_WARNING_DEFAULT;
363 153 0 : sgr_note = SGR_NOTE_DEFAULT;
1469 154 0 : sgr_locus = SGR_LOCUS_DEFAULT;
155 : }
156 : }
1469 peter 157 CBC 9526 : }
158 :
159 : /*
160 : * Change the logging flags.
161 : */
162 : void
163 12258 : pg_logging_config(int new_flags)
164 : {
165 12258 : log_flags = new_flags;
166 12258 : }
167 :
168 : /*
169 : * pg_logging_init sets the default log level to INFO. Programs that prefer
170 : * a different default should use this to set it, immediately afterward.
171 : */
172 : void
173 258 : pg_logging_set_level(enum pg_log_level new_level)
174 : {
175 258 : __pg_log_level = new_level;
176 258 : }
177 :
178 : /*
179 : * Command line switches such as --verbose should invoke this.
180 : */
181 : void
934 tgl 182 43 : pg_logging_increase_verbosity(void)
183 : {
184 : /*
185 : * The enum values are chosen such that we have to decrease __pg_log_level
186 : * in order to become more verbose.
187 : */
188 43 : if (__pg_log_level > PG_LOG_NOTSET + 1)
189 43 : __pg_log_level--;
190 43 : }
191 :
192 : void
1418 193 6482 : pg_logging_set_pre_callback(void (*cb) (void))
194 : {
1469 peter 195 6482 : log_pre_callback = cb;
196 6482 : }
197 :
198 : void
1418 tgl 199 6482 : pg_logging_set_locus_callback(void (*cb) (const char **filename, uint64 *lineno))
200 : {
1469 peter 201 6482 : log_locus_callback = cb;
202 6482 : }
203 :
204 : void
366 tgl 205 71897 : pg_log_generic(enum pg_log_level level, enum pg_log_part part,
206 : const char *pg_restrict fmt,...)
207 : {
208 : va_list ap;
209 :
1469 peter 210 71897 : va_start(ap, fmt);
366 tgl 211 71897 : pg_log_generic_v(level, part, fmt, ap);
1469 peter 212 71897 : va_end(ap);
213 71897 : }
214 :
215 : void
366 tgl 216 71949 : pg_log_generic_v(enum pg_log_level level, enum pg_log_part part,
217 : const char *pg_restrict fmt, va_list ap)
218 : {
1469 peter 219 71949 : int save_errno = errno;
220 71949 : const char *filename = NULL;
221 71949 : uint64 lineno = 0;
222 : va_list ap2;
223 : size_t required_len;
224 : char *buf;
225 :
226 71949 : Assert(progname);
227 71949 : Assert(level);
228 71949 : Assert(fmt);
229 71949 : Assert(fmt[strlen(fmt) - 1] != '\n');
230 :
231 : /* Do nothing if log level is too low. */
362 tgl 232 71949 : if (level < __pg_log_level)
233 27238 : return;
234 :
235 : /*
236 : * Flush stdout before output to stderr, to ensure sync even when stdout
237 : * is buffered.
238 : */
1469 peter 239 44711 : fflush(stdout);
240 :
241 44711 : if (log_pre_callback)
242 27375 : log_pre_callback();
243 :
244 44711 : if (log_locus_callback)
245 27375 : log_locus_callback(&filename, &lineno);
246 :
247 44711 : fmt = _(fmt);
248 :
314 249 44711 : if (!(log_flags & PG_LOG_FLAG_TERSE) || filename)
250 : {
1469 251 17937 : if (sgr_locus)
1469 peter 252 UBC 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_locus);
1469 peter 253 CBC 17937 : if (!(log_flags & PG_LOG_FLAG_TERSE))
254 17937 : fprintf(stderr, "%s:", progname);
255 17937 : if (filename)
256 : {
257 306 : fprintf(stderr, "%s:", filename);
258 306 : if (lineno > 0)
259 306 : fprintf(stderr, UINT64_FORMAT ":", lineno);
260 : }
261 17937 : fprintf(stderr, " ");
262 17937 : if (sgr_locus)
1469 peter 263 UBC 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
264 : }
265 :
1469 peter 266 CBC 44711 : if (!(log_flags & PG_LOG_FLAG_TERSE))
267 : {
366 tgl 268 17937 : switch (part)
269 : {
270 17766 : case PG_LOG_PRIMARY:
271 : switch (level)
272 : {
273 830 : case PG_LOG_ERROR:
274 830 : if (sgr_error)
366 tgl 275 UBC 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_error);
366 tgl 276 CBC 830 : fprintf(stderr, _("error: "));
277 830 : if (sgr_error)
366 tgl 278 UBC 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
366 tgl 279 CBC 830 : break;
280 132 : case PG_LOG_WARNING:
281 132 : if (sgr_warning)
366 tgl 282 UBC 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_warning);
366 tgl 283 CBC 132 : fprintf(stderr, _("warning: "));
284 132 : if (sgr_warning)
366 tgl 285 UBC 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
366 tgl 286 CBC 132 : break;
287 16804 : default:
288 16804 : break;
289 : }
1469 peter 290 17766 : break;
366 tgl 291 9 : case PG_LOG_DETAIL:
363 peter 292 9 : if (sgr_note)
363 peter 293 UBC 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_note);
366 tgl 294 CBC 9 : fprintf(stderr, _("detail: "));
363 peter 295 9 : if (sgr_note)
363 peter 296 UBC 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
1469 peter 297 CBC 9 : break;
366 tgl 298 162 : case PG_LOG_HINT:
363 peter 299 162 : if (sgr_note)
363 peter 300 UBC 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_note);
366 tgl 301 CBC 162 : fprintf(stderr, _("hint: "));
363 peter 302 162 : if (sgr_note)
363 peter 303 UBC 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
1469 peter 304 CBC 162 : break;
305 : }
306 : }
307 :
308 44711 : errno = save_errno;
309 :
310 44711 : va_copy(ap2, ap);
311 44711 : required_len = vsnprintf(NULL, 0, fmt, ap2) + 1;
312 44711 : va_end(ap2);
313 :
314 44711 : buf = pg_malloc_extended(required_len, MCXT_ALLOC_NO_OOM);
315 :
1373 tgl 316 44711 : errno = save_errno; /* malloc might change errno */
317 :
1469 peter 318 44711 : if (!buf)
319 : {
320 : /* memory trouble, just print what we can and get out of here */
1469 peter 321 UBC 0 : vfprintf(stderr, fmt, ap);
322 0 : return;
323 : }
324 :
1469 peter 325 CBC 44711 : vsnprintf(buf, required_len, fmt, ap);
326 :
327 : /* strip one newline, for PQerrorMessage() */
1426 tgl 328 44711 : if (required_len >= 2 && buf[required_len - 2] == '\n')
1469 peter 329 26872 : buf[required_len - 2] = '\0';
330 :
331 44711 : fprintf(stderr, "%s\n", buf);
332 :
333 44711 : free(buf);
334 : }
|