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