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