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
85 15608 : pg_logging_init(const char *argv0)
86 : {
87 15608 : const char *pg_color_env = getenv("PG_COLOR");
88 15608 : bool log_color = false;
89 15608 : 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 */
102 15608 : setvbuf(stderr, NULL, _IONBF, 0);
103 :
104 15608 : progname = get_progname(argv0);
105 15608 : __pg_log_level = PG_LOG_INFO;
106 :
107 15608 : if (pg_color_env)
108 : {
109 0 : if (strcmp(pg_color_env, "always") == 0 ||
110 0 : (strcmp(pg_color_env, "auto") == 0 && color_terminal))
111 0 : log_color = true;
112 : }
113 :
114 15608 : if (log_color)
115 : {
116 0 : const char *pg_colors_env = getenv("PG_COLORS");
117 :
118 0 : if (pg_colors_env)
119 : {
120 0 : char *colors = strdup(pg_colors_env);
121 :
122 0 : if (colors)
123 : {
124 : char *token;
125 0 : char *cp = colors;
126 :
127 0 : while ((token = strsep(&cp, ":")))
128 : {
129 0 : char *e = strchr(token, '=');
130 :
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);
144 0 : if (strcmp(name, "note") == 0)
145 0 : sgr_note = strdup(value);
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;
158 0 : sgr_note = SGR_NOTE_DEFAULT;
159 0 : sgr_locus = SGR_LOCUS_DEFAULT;
160 : }
161 : }
162 15608 : }
163 :
164 : /*
165 : * Change the logging flags.
166 : */
167 : void
168 20125 : pg_logging_config(int new_flags)
169 : {
170 20125 : log_flags = new_flags;
171 20125 : }
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 631 : pg_logging_set_level(enum pg_log_level new_level)
179 : {
180 631 : __pg_log_level = new_level;
181 631 : }
182 :
183 : /*
184 : * Command line switches such as --verbose should invoke this.
185 : */
186 : void
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
198 10347 : pg_logging_set_pre_callback(void (*cb) (void))
199 : {
200 10347 : log_pre_callback = cb;
201 10347 : }
202 :
203 : void
204 10347 : pg_logging_set_locus_callback(void (*cb) (const char **filename, uint64 *lineno))
205 : {
206 10347 : log_locus_callback = cb;
207 10347 : }
208 :
209 : void
210 1 : pg_logging_set_logfile(FILE *logfile)
211 : {
212 1 : log_logfile = logfile;
213 1 : }
214 :
215 : void
216 0 : pg_logging_unset_logfile(void)
217 : {
218 0 : log_logfile = NULL;
219 0 : }
220 :
221 : void
222 299444 : 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 :
227 299444 : va_start(ap, fmt);
228 299444 : pg_log_generic_v(level, part, fmt, ap);
229 299444 : va_end(ap);
230 299444 : }
231 :
232 : void
233 300564 : pg_log_generic_v(enum pg_log_level level, enum pg_log_part part,
234 : const char *pg_restrict fmt, va_list ap)
235 : {
236 300564 : int save_errno = errno;
237 300564 : const char *filename = NULL;
238 300564 : uint64 lineno = 0;
239 : va_list ap2;
240 : size_t required_len;
241 : char *buf;
242 :
243 : Assert(progname);
244 : Assert(level);
245 : Assert(fmt);
246 : Assert(fmt[strlen(fmt) - 1] != '\n');
247 :
248 : /* Do nothing if log level is too low. */
249 300564 : if (level < __pg_log_level)
250 54952 : return;
251 :
252 : /*
253 : * Flush stdout before output to stderr, to ensure sync even when stdout
254 : * is buffered.
255 : */
256 245612 : fflush(stdout);
257 :
258 245612 : if (log_pre_callback)
259 216705 : log_pre_callback();
260 :
261 245612 : if (log_locus_callback)
262 216705 : log_locus_callback(&filename, &lineno);
263 :
264 245612 : fmt = _(fmt);
265 :
266 245612 : if (!(log_flags & PG_LOG_FLAG_TERSE) || filename)
267 : {
268 198874 : if (sgr_locus)
269 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_locus);
270 198874 : if (!(log_flags & PG_LOG_FLAG_TERSE))
271 198874 : fprintf(stderr, "%s:", progname);
272 198874 : if (filename)
273 : {
274 169767 : fprintf(stderr, "%s:", filename);
275 169767 : if (lineno > 0)
276 169767 : fprintf(stderr, UINT64_FORMAT ":", lineno);
277 : }
278 198874 : fprintf(stderr, " ");
279 198874 : if (sgr_locus)
280 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
281 : }
282 :
283 245612 : if (!(log_flags & PG_LOG_FLAG_TERSE))
284 : {
285 198874 : switch (part)
286 : {
287 198717 : case PG_LOG_PRIMARY:
288 198717 : switch (level)
289 : {
290 1986 : case PG_LOG_ERROR:
291 1986 : if (sgr_error)
292 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_error);
293 1986 : fprintf(stderr, _("error: "));
294 1986 : if (log_logfile)
295 0 : fprintf(log_logfile, _("error: "));
296 1986 : if (sgr_error)
297 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
298 1986 : break;
299 140 : case PG_LOG_WARNING:
300 140 : if (sgr_warning)
301 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_warning);
302 140 : fprintf(stderr, _("warning: "));
303 140 : if (log_logfile)
304 0 : fprintf(log_logfile, _("warning: "));
305 140 : if (sgr_warning)
306 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
307 140 : break;
308 196591 : default:
309 196591 : break;
310 : }
311 198717 : break;
312 18 : case PG_LOG_DETAIL:
313 18 : if (sgr_note)
314 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_note);
315 18 : fprintf(stderr, _("detail: "));
316 18 : if (log_logfile)
317 0 : fprintf(log_logfile, _("detail: "));
318 18 : if (sgr_note)
319 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
320 18 : break;
321 139 : case PG_LOG_HINT:
322 139 : if (sgr_note)
323 0 : fprintf(stderr, ANSI_ESCAPE_FMT, sgr_note);
324 139 : fprintf(stderr, _("hint: "));
325 139 : if (log_logfile)
326 1 : fprintf(log_logfile, _("hint: "));
327 139 : if (sgr_note)
328 0 : fprintf(stderr, ANSI_ESCAPE_RESET);
329 139 : break;
330 : }
331 : }
332 :
333 245612 : errno = save_errno;
334 :
335 245612 : va_copy(ap2, ap);
336 245612 : required_len = vsnprintf(NULL, 0, fmt, ap2) + 1;
337 245612 : va_end(ap2);
338 :
339 245612 : buf = pg_malloc_extended(required_len, MCXT_ALLOC_NO_OOM);
340 :
341 245612 : errno = save_errno; /* malloc might change errno */
342 :
343 245612 : if (!buf)
344 : {
345 : /* memory trouble, just print what we can and get out of here */
346 0 : vfprintf(stderr, fmt, ap);
347 0 : return;
348 : }
349 :
350 245612 : vsnprintf(buf, required_len, fmt, ap);
351 :
352 : /* strip one newline, for PQerrorMessage() */
353 245612 : if (required_len >= 2 && buf[required_len - 2] == '\n')
354 215899 : buf[required_len - 2] = '\0';
355 :
356 245612 : fprintf(stderr, "%s\n", buf);
357 245612 : if (log_logfile)
358 : {
359 40 : fprintf(log_logfile, "%s\n", buf);
360 40 : fflush(log_logfile);
361 : }
362 :
363 245612 : free(buf);
364 : }
|