1/*-------------------------------------------------------------------------
2 * Logging framework for frontend programs
3 *
4 * Copyright (c) 2018-2019, 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
16enum pg_log_level __pg_log_level;
17
18static const char *progname;
19static int log_flags;
20
21static void (*log_pre_callback) (void);
22static void (*log_locus_callback) (const char **, uint64 *);
23
24static const char *sgr_error = NULL;
25static const char *sgr_warning = NULL;
26static 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/*
36 * This should be called before any output happens.
37 */
38void
39pg_logging_init(const char *argv0)
40{
41 const char *pg_color_env = getenv("PG_COLOR");
42 bool log_color = false;
43
44 /* usually the default, but not on Windows */
45 setvbuf(stderr, NULL, _IONBF, 0);
46
47 progname = get_progname(argv0);
48 __pg_log_level = PG_LOG_INFO;
49
50 if (pg_color_env)
51 {
52 if (strcmp(pg_color_env, "always") == 0 ||
53 (strcmp(pg_color_env, "auto") == 0 && isatty(fileno(stderr))))
54 log_color = true;
55 }
56
57 if (log_color)
58 {
59 const char *pg_colors_env = getenv("PG_COLORS");
60
61 if (pg_colors_env)
62 {
63 char *colors = strdup(pg_colors_env);
64
65 if (colors)
66 {
67 for (char *token = strtok(colors, ":"); token; token = strtok(NULL, ":"))
68 {
69 char *e = strchr(token, '=');
70
71 if (e)
72 {
73 char *name;
74 char *value;
75
76 *e = '\0';
77 name = token;
78 value = e + 1;
79
80 if (strcmp(name, "error") == 0)
81 sgr_error = strdup(value);
82 if (strcmp(name, "warning") == 0)
83 sgr_warning = strdup(value);
84 if (strcmp(name, "locus") == 0)
85 sgr_locus = strdup(value);
86 }
87 }
88
89 free(colors);
90 }
91 }
92 else
93 {
94 sgr_error = SGR_ERROR_DEFAULT;
95 sgr_warning = SGR_WARNING_DEFAULT;
96 sgr_locus = SGR_LOCUS_DEFAULT;
97 }
98 }
99}
100
101void
102pg_logging_config(int new_flags)
103{
104 log_flags = new_flags;
105}
106
107void
108pg_logging_set_level(enum pg_log_level new_level)
109{
110 __pg_log_level = new_level;
111}
112
113void
114pg_logging_set_pre_callback(void (*cb) (void))
115{
116 log_pre_callback = cb;
117}
118
119void
120pg_logging_set_locus_callback(void (*cb) (const char **filename, uint64 *lineno))
121{
122 log_locus_callback = cb;
123}
124
125void
126pg_log_generic(enum pg_log_level level, const char *pg_restrict fmt,...)
127{
128 va_list ap;
129
130 va_start(ap, fmt);
131 pg_log_generic_v(level, fmt, ap);
132 va_end(ap);
133}
134
135void
136pg_log_generic_v(enum pg_log_level level, const char *pg_restrict fmt, va_list ap)
137{
138 int save_errno = errno;
139 const char *filename = NULL;
140 uint64 lineno = 0;
141 va_list ap2;
142 size_t required_len;
143 char *buf;
144
145 Assert(progname);
146 Assert(level);
147 Assert(fmt);
148 Assert(fmt[strlen(fmt) - 1] != '\n');
149
150 /*
151 * Flush stdout before output to stderr, to ensure sync even when stdout
152 * is buffered.
153 */
154 fflush(stdout);
155
156 if (log_pre_callback)
157 log_pre_callback();
158
159 if (log_locus_callback)
160 log_locus_callback(&filename, &lineno);
161
162 fmt = _(fmt);
163
164 if (!(log_flags & PG_LOG_FLAG_TERSE) || filename)
165 {
166 if (sgr_locus)
167 fprintf(stderr, ANSI_ESCAPE_FMT, sgr_locus);
168 if (!(log_flags & PG_LOG_FLAG_TERSE))
169 fprintf(stderr, "%s:", progname);
170 if (filename)
171 {
172 fprintf(stderr, "%s:", filename);
173 if (lineno > 0)
174 fprintf(stderr, UINT64_FORMAT ":", lineno);
175 }
176 fprintf(stderr, " ");
177 if (sgr_locus)
178 fprintf(stderr, ANSI_ESCAPE_RESET);
179 }
180
181 if (!(log_flags & PG_LOG_FLAG_TERSE))
182 {
183 switch (level)
184 {
185 case PG_LOG_FATAL:
186 if (sgr_error)
187 fprintf(stderr, ANSI_ESCAPE_FMT, sgr_error);
188 fprintf(stderr, _("fatal: "));
189 if (sgr_error)
190 fprintf(stderr, ANSI_ESCAPE_RESET);
191 break;
192 case PG_LOG_ERROR:
193 if (sgr_error)
194 fprintf(stderr, ANSI_ESCAPE_FMT, sgr_error);
195 fprintf(stderr, _("error: "));
196 if (sgr_error)
197 fprintf(stderr, ANSI_ESCAPE_RESET);
198 break;
199 case PG_LOG_WARNING:
200 if (sgr_warning)
201 fprintf(stderr, ANSI_ESCAPE_FMT, sgr_warning);
202 fprintf(stderr, _("warning: "));
203 if (sgr_warning)
204 fprintf(stderr, ANSI_ESCAPE_RESET);
205 break;
206 default:
207 break;
208 }
209 }
210
211 errno = save_errno;
212
213 va_copy(ap2, ap);
214 required_len = vsnprintf(NULL, 0, fmt, ap2) + 1;
215 va_end(ap2);
216
217 buf = pg_malloc_extended(required_len, MCXT_ALLOC_NO_OOM);
218
219 errno = save_errno; /* malloc might change errno */
220
221 if (!buf)
222 {
223 /* memory trouble, just print what we can and get out of here */
224 vfprintf(stderr, fmt, ap);
225 return;
226 }
227
228 vsnprintf(buf, required_len, fmt, ap);
229
230 /* strip one newline, for PQerrorMessage() */
231 if (required_len >= 2 && buf[required_len - 2] == '\n')
232 buf[required_len - 2] = '\0';
233
234 fprintf(stderr, "%s\n", buf);
235
236 free(buf);
237}
238