Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : * Logging framework for frontend programs
3 : : *
4 : : * Copyright (c) 2018-2025, 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
2350 peter@eisentraut.org 83 :CBC 14544 : pg_logging_init(const char *argv0)
84 : : {
85 : 14544 : const char *pg_color_env = getenv("PG_COLOR");
86 : 14544 : bool log_color = false;
2014 michael@paquier.xyz 87 : 14544 : 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 */
2350 peter@eisentraut.org 100 : 14544 : setvbuf(stderr, NULL, _IONBF, 0);
101 : :
102 : 14544 : progname = get_progname(argv0);
103 : 14544 : __pg_log_level = PG_LOG_INFO;
104 : :
105 [ - + ]: 14544 : if (pg_color_env)
106 : : {
2350 peter@eisentraut.org 107 [ # # ]:UBC 0 : if (strcmp(pg_color_env, "always") == 0 ||
2014 michael@paquier.xyz 108 [ # # # # ]: 0 : (strcmp(pg_color_env, "auto") == 0 && color_terminal))
2350 peter@eisentraut.org 109 : 0 : log_color = true;
110 : : }
111 : :
2350 peter@eisentraut.org 112 [ - + ]:CBC 14544 : if (log_color)
113 : : {
2350 peter@eisentraut.org 114 :UBC 0 : const char *pg_colors_env = getenv("PG_COLORS");
115 : :
116 [ # # ]: 0 : if (pg_colors_env)
117 : : {
2299 tgl@sss.pgh.pa.us 118 : 0 : char *colors = strdup(pg_colors_env);
119 : :
2350 peter@eisentraut.org 120 [ # # ]: 0 : if (colors)
121 : : {
122 : : char *token;
323 123 : 0 : char *cp = colors;
124 : :
125 [ # # ]: 0 : while ((token = strsep(&cp, ":")))
126 : : {
2299 tgl@sss.pgh.pa.us 127 : 0 : char *e = strchr(token, '=');
128 : :
2350 peter@eisentraut.org 129 [ # # ]: 0 : if (e)
130 : : {
131 : : char *name;
132 : : char *value;
133 : :
134 : 0 : *e = '\0';
135 : 0 : name = token;
136 : 0 : value = e + 1;
137 : :
138 [ # # ]: 0 : if (strcmp(name, "error") == 0)
139 : 0 : sgr_error = strdup(value);
140 [ # # ]: 0 : if (strcmp(name, "warning") == 0)
141 : 0 : sgr_warning = strdup(value);
1244 142 [ # # ]: 0 : if (strcmp(name, "note") == 0)
143 : 0 : sgr_note = strdup(value);
2350 144 [ # # ]: 0 : if (strcmp(name, "locus") == 0)
145 : 0 : sgr_locus = strdup(value);
146 : : }
147 : : }
148 : :
149 : 0 : free(colors);
150 : : }
151 : : }
152 : : else
153 : : {
154 : 0 : sgr_error = SGR_ERROR_DEFAULT;
155 : 0 : sgr_warning = SGR_WARNING_DEFAULT;
1244 156 : 0 : sgr_note = SGR_NOTE_DEFAULT;
2350 157 : 0 : sgr_locus = SGR_LOCUS_DEFAULT;
158 : : }
159 : : }
2350 peter@eisentraut.org 160 :CBC 14544 : }
161 : :
162 : : /*
163 : : * Change the logging flags.
164 : : */
165 : : void
166 : 16336 : pg_logging_config(int new_flags)
167 : : {
168 : 16336 : log_flags = new_flags;
169 : 16336 : }
170 : :
171 : : /*
172 : : * pg_logging_init sets the default log level to INFO. Programs that prefer
173 : : * a different default should use this to set it, immediately afterward.
174 : : */
175 : : void
176 : 472 : pg_logging_set_level(enum pg_log_level new_level)
177 : : {
178 : 472 : __pg_log_level = new_level;
179 : 472 : }
180 : :
181 : : /*
182 : : * Command line switches such as --verbose should invoke this.
183 : : */
184 : : void
1815 tgl@sss.pgh.pa.us 185 : 90 : pg_logging_increase_verbosity(void)
186 : : {
187 : : /*
188 : : * The enum values are chosen such that we have to decrease __pg_log_level
189 : : * in order to become more verbose.
190 : : */
191 [ + - ]: 90 : if (__pg_log_level > PG_LOG_NOTSET + 1)
192 : 90 : __pg_log_level--;
193 : 90 : }
194 : :
195 : : void
2299 196 : 8504 : pg_logging_set_pre_callback(void (*cb) (void))
197 : : {
2350 peter@eisentraut.org 198 : 8504 : log_pre_callback = cb;
199 : 8504 : }
200 : :
201 : : void
2299 tgl@sss.pgh.pa.us 202 : 8504 : pg_logging_set_locus_callback(void (*cb) (const char **filename, uint64 *lineno))
203 : : {
2350 peter@eisentraut.org 204 : 8504 : log_locus_callback = cb;
205 : 8504 : }
206 : :
207 : : void
1247 tgl@sss.pgh.pa.us 208 : 110660 : pg_log_generic(enum pg_log_level level, enum pg_log_part part,
209 : : const char *pg_restrict fmt,...)
210 : : {
211 : : va_list ap;
212 : :
2350 peter@eisentraut.org 213 : 110660 : va_start(ap, fmt);
1247 tgl@sss.pgh.pa.us 214 : 110660 : pg_log_generic_v(level, part, fmt, ap);
2350 peter@eisentraut.org 215 : 110660 : va_end(ap);
216 : 110660 : }
217 : :
218 : : void
1247 tgl@sss.pgh.pa.us 219 : 111714 : pg_log_generic_v(enum pg_log_level level, enum pg_log_part part,
220 : : const char *pg_restrict fmt, va_list ap)
221 : : {
2350 peter@eisentraut.org 222 : 111714 : int save_errno = errno;
223 : 111714 : const char *filename = NULL;
224 : 111714 : uint64 lineno = 0;
225 : : va_list ap2;
226 : : size_t required_len;
227 : : char *buf;
228 : :
229 [ - + ]: 111714 : Assert(progname);
230 [ - + ]: 111714 : Assert(level);
231 [ - + ]: 111714 : Assert(fmt);
232 [ - + ]: 111714 : Assert(fmt[strlen(fmt) - 1] != '\n');
233 : :
234 : : /* Do nothing if log level is too low. */
1243 tgl@sss.pgh.pa.us 235 [ + + ]: 111714 : if (level < __pg_log_level)
236 : 52051 : return;
237 : :
238 : : /*
239 : : * Flush stdout before output to stderr, to ensure sync even when stdout
240 : : * is buffered.
241 : : */
2350 peter@eisentraut.org 242 : 59663 : fflush(stdout);
243 : :
244 [ + + ]: 59663 : if (log_pre_callback)
245 : 32829 : log_pre_callback();
246 : :
247 [ + + ]: 59663 : if (log_locus_callback)
248 : 32829 : log_locus_callback(&filename, &lineno);
249 : :
250 : 59663 : fmt = _(fmt);
251 : :
1195 252 [ + + - + ]: 59663 : if (!(log_flags & PG_LOG_FLAG_TERSE) || filename)
253 : : {
2350 254 [ - + ]: 27787 : if (sgr_locus)
2350 peter@eisentraut.org 255 :UBC 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_locus);
2350 peter@eisentraut.org 256 [ + - ]:CBC 27787 : if (!(log_flags & PG_LOG_FLAG_TERSE))
257 : 27787 : fprintf(stderr, "%s:", progname);
258 [ + + ]: 27787 : if (filename)
259 : : {
260 : 652 : fprintf(stderr, "%s:", filename);
261 [ + - ]: 652 : if (lineno > 0)
262 : 652 : fprintf(stderr, UINT64_FORMAT ":", lineno);
263 : : }
264 : 27787 : fprintf(stderr, " ");
265 [ - + ]: 27787 : if (sgr_locus)
2350 peter@eisentraut.org 266 :UBC 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
267 : : }
268 : :
2350 peter@eisentraut.org 269 [ + + ]:CBC 59663 : if (!(log_flags & PG_LOG_FLAG_TERSE))
270 : : {
1247 tgl@sss.pgh.pa.us 271 [ + + + - ]: 27787 : switch (part)
272 : : {
273 : 27635 : case PG_LOG_PRIMARY:
274 [ + + + ]: 27635 : switch (level)
275 : : {
276 : 1937 : case PG_LOG_ERROR:
277 [ - + ]: 1937 : if (sgr_error)
1247 tgl@sss.pgh.pa.us 278 :UBC 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_error);
1247 tgl@sss.pgh.pa.us 279 :CBC 1937 : fprintf(stderr, _("error: "));
280 [ - + ]: 1937 : if (sgr_error)
1247 tgl@sss.pgh.pa.us 281 :UBC 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
1247 tgl@sss.pgh.pa.us 282 :CBC 1937 : break;
283 : 140 : case PG_LOG_WARNING:
284 [ - + ]: 140 : if (sgr_warning)
1247 tgl@sss.pgh.pa.us 285 :UBC 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_warning);
1247 tgl@sss.pgh.pa.us 286 :CBC 140 : fprintf(stderr, _("warning: "));
287 [ - + ]: 140 : if (sgr_warning)
1247 tgl@sss.pgh.pa.us 288 :UBC 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
1247 tgl@sss.pgh.pa.us 289 :CBC 140 : break;
290 : 25558 : default:
291 : 25558 : break;
292 : : }
2350 peter@eisentraut.org 293 : 27635 : break;
1247 tgl@sss.pgh.pa.us 294 : 18 : case PG_LOG_DETAIL:
1244 peter@eisentraut.org 295 [ - + ]: 18 : if (sgr_note)
1244 peter@eisentraut.org 296 :UBC 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_note);
1247 tgl@sss.pgh.pa.us 297 :CBC 18 : fprintf(stderr, _("detail: "));
1244 peter@eisentraut.org 298 [ - + ]: 18 : if (sgr_note)
1244 peter@eisentraut.org 299 :UBC 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
2350 peter@eisentraut.org 300 :CBC 18 : break;
1247 tgl@sss.pgh.pa.us 301 : 134 : case PG_LOG_HINT:
1244 peter@eisentraut.org 302 [ - + ]: 134 : if (sgr_note)
1244 peter@eisentraut.org 303 :UBC 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_note);
1247 tgl@sss.pgh.pa.us 304 :CBC 134 : fprintf(stderr, _("hint: "));
1244 peter@eisentraut.org 305 [ - + ]: 134 : if (sgr_note)
1244 peter@eisentraut.org 306 :UBC 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
2350 peter@eisentraut.org 307 :CBC 134 : break;
308 : : }
309 : : }
310 : :
311 : 59663 : errno = save_errno;
312 : :
313 : 59663 : va_copy(ap2, ap);
314 : 59663 : required_len = vsnprintf(NULL, 0, fmt, ap2) + 1;
315 : 59663 : va_end(ap2);
316 : :
317 : 59663 : buf = pg_malloc_extended(required_len, MCXT_ALLOC_NO_OOM);
318 : :
2254 tgl@sss.pgh.pa.us 319 : 59663 : errno = save_errno; /* malloc might change errno */
320 : :
2350 peter@eisentraut.org 321 [ - + ]: 59663 : if (!buf)
322 : : {
323 : : /* memory trouble, just print what we can and get out of here */
2350 peter@eisentraut.org 324 :UBC 0 : vfprintf(stderr, fmt, ap);
325 : 0 : return;
326 : : }
327 : :
2350 peter@eisentraut.org 328 :CBC 59663 : vsnprintf(buf, required_len, fmt, ap);
329 : :
330 : : /* strip one newline, for PQerrorMessage() */
2307 tgl@sss.pgh.pa.us 331 [ + - + + ]: 59663 : if (required_len >= 2 && buf[required_len - 2] == '\n')
2350 peter@eisentraut.org 332 : 32227 : buf[required_len - 2] = '\0';
333 : :
334 : 59663 : fprintf(stderr, "%s\n", buf);
335 : :
336 : 59663 : free(buf);
337 : : }
|