1 /*-------------------------------------------------------------------------
2 * Logging framework for frontend programs
3 *
4 * Copyright (c) 2018-2020, PostgreSQL Global Development Group
5 *
6 * src/common/logging.c
7 *
8 *-------------------------------------------------------------------------
9 */
10 #include "postgres_fe.h"
11
12 #include <unistd.h>
13
14 #include "common/logging.h"
15
16 enum pg_log_level __pg_log_level;
17
18 static const char *progname;
19 static int log_flags;
20
21 static void (*log_pre_callback) (void);
22 static void (*log_locus_callback) (const char **, uint64 *);
23
24 static const char *sgr_error = NULL;
25 static const char *sgr_warning = NULL;
26 static const char *sgr_locus = NULL;
27
28 #define SGR_ERROR_DEFAULT "01;31"
29 #define SGR_WARNING_DEFAULT "01;35"
30 #define SGR_LOCUS_DEFAULT "01"
31
32 #define ANSI_ESCAPE_FMT "\x1b[%sm"
33 #define ANSI_ESCAPE_RESET "\x1b[0m"
34
35 #ifdef WIN32
36
37 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
38 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
39 #endif
40
41 /*
42 * Attempt to enable VT100 sequence processing for colorization on Windows.
43 * If current environment is not VT100-compatible or if this mode could not
44 * be enabled, return false.
45 */
46 static bool
enable_vt_processing(void)47 enable_vt_processing(void)
48 {
49 /* Check stderr */
50 HANDLE hOut = GetStdHandle(STD_ERROR_HANDLE);
51 DWORD dwMode = 0;
52
53 if (hOut == INVALID_HANDLE_VALUE)
54 return false;
55
56 /*
57 * Look for the current console settings and check if VT100 is already
58 * enabled.
59 */
60 if (!GetConsoleMode(hOut, &dwMode))
61 return false;
62 if ((dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0)
63 return true;
64
65 dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
66 if (!SetConsoleMode(hOut, dwMode))
67 return false;
68 return true;
69 }
70 #endif /* WIN32 */
71
72 /*
73 * This should be called before any output happens.
74 */
75 void
pg_logging_init(const char * argv0)76 pg_logging_init(const char *argv0)
77 {
78 const char *pg_color_env = getenv("PG_COLOR");
79 bool log_color = false;
80 bool color_terminal = isatty(fileno(stderr));
81
82 #ifdef WIN32
83
84 /*
85 * On Windows, check if environment is VT100-compatible if using a
86 * terminal.
87 */
88 if (color_terminal)
89 color_terminal = enable_vt_processing();
90 #endif
91
92 /* usually the default, but not on Windows */
93 setvbuf(stderr, NULL, _IONBF, 0);
94
95 progname = get_progname(argv0);
96 __pg_log_level = PG_LOG_INFO;
97
98 if (pg_color_env)
99 {
100 if (strcmp(pg_color_env, "always") == 0 ||
101 (strcmp(pg_color_env, "auto") == 0 && color_terminal))
102 log_color = true;
103 }
104
105 if (log_color)
106 {
107 const char *pg_colors_env = getenv("PG_COLORS");
108
109 if (pg_colors_env)
110 {
111 char *colors = strdup(pg_colors_env);
112
113 if (colors)
114 {
115 for (char *token = strtok(colors, ":"); token; token = strtok(NULL, ":"))
116 {
117 char *e = strchr(token, '=');
118
119 if (e)
120 {
121 char *name;
122 char *value;
123
124 *e = '\0';
125 name = token;
126 value = e + 1;
127
128 if (strcmp(name, "error") == 0)
129 sgr_error = strdup(value);
130 if (strcmp(name, "warning") == 0)
131 sgr_warning = strdup(value);
132 if (strcmp(name, "locus") == 0)
133 sgr_locus = strdup(value);
134 }
135 }
136
137 free(colors);
138 }
139 }
140 else
141 {
142 sgr_error = SGR_ERROR_DEFAULT;
143 sgr_warning = SGR_WARNING_DEFAULT;
144 sgr_locus = SGR_LOCUS_DEFAULT;
145 }
146 }
147 }
148
149 void
pg_logging_config(int new_flags)150 pg_logging_config(int new_flags)
151 {
152 log_flags = new_flags;
153 }
154
155 void
pg_logging_set_level(enum pg_log_level new_level)156 pg_logging_set_level(enum pg_log_level new_level)
157 {
158 __pg_log_level = new_level;
159 }
160
161 void
pg_logging_set_pre_callback(void (* cb)(void))162 pg_logging_set_pre_callback(void (*cb) (void))
163 {
164 log_pre_callback = cb;
165 }
166
167 void
pg_logging_set_locus_callback(void (* cb)(const char ** filename,uint64 * lineno))168 pg_logging_set_locus_callback(void (*cb) (const char **filename, uint64 *lineno))
169 {
170 log_locus_callback = cb;
171 }
172
173 void
pg_log_generic(enum pg_log_level level,const char * pg_restrict fmt,...)174 pg_log_generic(enum pg_log_level level, const char *pg_restrict fmt,...)
175 {
176 va_list ap;
177
178 va_start(ap, fmt);
179 pg_log_generic_v(level, fmt, ap);
180 va_end(ap);
181 }
182
183 void
pg_log_generic_v(enum pg_log_level level,const char * pg_restrict fmt,va_list ap)184 pg_log_generic_v(enum pg_log_level level, const char *pg_restrict fmt, va_list ap)
185 {
186 int save_errno = errno;
187 const char *filename = NULL;
188 uint64 lineno = 0;
189 va_list ap2;
190 size_t required_len;
191 char *buf;
192
193 Assert(progname);
194 Assert(level);
195 Assert(fmt);
196 Assert(fmt[strlen(fmt) - 1] != '\n');
197
198 /*
199 * Flush stdout before output to stderr, to ensure sync even when stdout
200 * is buffered.
201 */
202 fflush(stdout);
203
204 if (log_pre_callback)
205 log_pre_callback();
206
207 if (log_locus_callback)
208 log_locus_callback(&filename, &lineno);
209
210 fmt = _(fmt);
211
212 if (!(log_flags & PG_LOG_FLAG_TERSE) || filename)
213 {
214 if (sgr_locus)
215 fprintf(stderr, ANSI_ESCAPE_FMT, sgr_locus);
216 if (!(log_flags & PG_LOG_FLAG_TERSE))
217 fprintf(stderr, "%s:", progname);
218 if (filename)
219 {
220 fprintf(stderr, "%s:", filename);
221 if (lineno > 0)
222 fprintf(stderr, UINT64_FORMAT ":", lineno);
223 }
224 fprintf(stderr, " ");
225 if (sgr_locus)
226 fprintf(stderr, ANSI_ESCAPE_RESET);
227 }
228
229 if (!(log_flags & PG_LOG_FLAG_TERSE))
230 {
231 switch (level)
232 {
233 case PG_LOG_FATAL:
234 if (sgr_error)
235 fprintf(stderr, ANSI_ESCAPE_FMT, sgr_error);
236 fprintf(stderr, _("fatal: "));
237 if (sgr_error)
238 fprintf(stderr, ANSI_ESCAPE_RESET);
239 break;
240 case PG_LOG_ERROR:
241 if (sgr_error)
242 fprintf(stderr, ANSI_ESCAPE_FMT, sgr_error);
243 fprintf(stderr, _("error: "));
244 if (sgr_error)
245 fprintf(stderr, ANSI_ESCAPE_RESET);
246 break;
247 case PG_LOG_WARNING:
248 if (sgr_warning)
249 fprintf(stderr, ANSI_ESCAPE_FMT, sgr_warning);
250 fprintf(stderr, _("warning: "));
251 if (sgr_warning)
252 fprintf(stderr, ANSI_ESCAPE_RESET);
253 break;
254 default:
255 break;
256 }
257 }
258
259 errno = save_errno;
260
261 va_copy(ap2, ap);
262 required_len = vsnprintf(NULL, 0, fmt, ap2) + 1;
263 va_end(ap2);
264
265 buf = pg_malloc_extended(required_len, MCXT_ALLOC_NO_OOM);
266
267 errno = save_errno; /* malloc might change errno */
268
269 if (!buf)
270 {
271 /* memory trouble, just print what we can and get out of here */
272 vfprintf(stderr, fmt, ap);
273 return;
274 }
275
276 vsnprintf(buf, required_len, fmt, ap);
277
278 /* strip one newline, for PQerrorMessage() */
279 if (required_len >= 2 && buf[required_len - 2] == '\n')
280 buf[required_len - 2] = '\0';
281
282 fprintf(stderr, "%s\n", buf);
283
284 free(buf);
285 }
286