1 /*
2 * Copyright 2019-2026 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <ctype.h>
13 #include <stdarg.h>
14 #include <stdbool.h>
15 #include <stdint.h>
16 #include <stdlib.h>
17 #include <stdio.h>
18
19 typedef struct {
20 /* gathered in log_begin_list */
21 GQueue/*<char*>*/ *prefixes;
22 uint8_t log_level;
23 const char *function;
24 const char *file;
25 uint32_t line;
26 uint32_t tags;
27 } private_data_t;
28
29 /*!
30 * \internal
31 * \brief Log a message using output object's log level and filters
32 *
33 * \param[in] priv Output object's private_data_t
34 * \param[in] fmt printf(3)-style format string
35 * \param[in] args... Format string arguments
36 */
37 #define logger(priv, fmt, args...) do { \
38 qb_log_from_external_source(pcmk__s((priv)->function, __func__), \
39 pcmk__s((priv)->file, __FILE__), fmt, (priv)->log_level, \
40 (((priv)->line == 0)? __LINE__ : (priv)->line), (priv)->tags, \
41 ##args); \
42 } while (0);
43
44 /*!
45 * \internal
46 * \brief Log a message using an explicit log level and output object's filters
47 *
48 * \param[in] priv Output object's private_data_t
49 * \param[in] level Log level
50 * \param[in] fmt printf(3)-style format string
51 * \param[in] ap Variadic arguments
52 */
53 #define logger_va(priv, level, fmt, ap) do { \
54 qb_log_from_external_source_va(pcmk__s((priv)->function, __func__), \
55 pcmk__s((priv)->file, __FILE__), fmt, level, \
56 (((priv)->line == 0)? __LINE__ : (priv)->line), (priv)->tags, \
57 ap); \
58 } while (0);
59
60 static void
61 log_subprocess_output(pcmk__output_t *out, int exit_status,
62 const char *proc_stdout, const char *proc_stderr) {
63 /* This function intentionally left blank */
64 }
65
66 static void
67 log_free_priv(pcmk__output_t *out) {
68 private_data_t *priv = NULL;
69
|
(1) Event path: |
Condition "out == NULL", taking false branch. |
|
(2) Event path: |
Condition "out->priv == NULL", taking false branch. |
70 if (out == NULL || out->priv == NULL) {
71 return;
72 }
73
74 priv = out->priv;
75
76 g_queue_free(priv->prefixes);
|
CID (unavailable; MK=a40dffd9e45b8a5bb8bfb65db94145f3) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(3) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(4) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
77 g_clear_pointer(&out->priv, free);
78 }
79
80 static bool
81 log_init(pcmk__output_t *out) {
82 private_data_t *priv = NULL;
83
84 pcmk__assert(out != NULL);
85
86 /* If log_init was previously called on this output struct, just return. */
87 if (out->priv != NULL) {
88 return true;
89 }
90
91 out->priv = calloc(1, sizeof(private_data_t));
92 if (out->priv == NULL) {
93 return false;
94 }
95
96 priv = out->priv;
97
98 priv->prefixes = g_queue_new();
99 priv->log_level = LOG_INFO;
100
101 return true;
102 }
103
104 static void
105 log_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
106 /* This function intentionally left blank */
107 }
108
109 static void
110 log_reset(pcmk__output_t *out) {
111 pcmk__assert(out != NULL);
112
113 out->dest = freopen(NULL, "w", out->dest);
114 pcmk__assert(out->dest != NULL);
115
116 log_free_priv(out);
117 log_init(out);
118 }
119
120 static void
121 log_version(pcmk__output_t *out)
122 {
123 private_data_t *priv = NULL;
124
125 pcmk__assert((out != NULL) && (out->priv != NULL));
126 priv = out->priv;
127
128 logger(priv, "Pacemaker " PACEMAKER_VERSION);
129 logger(priv,
130 "Written by Andrew Beekhof and the Pacemaker project contributors");
131 }
132
133 G_GNUC_PRINTF(2, 3)
134 static void
135 log_err(pcmk__output_t *out, const char *format, ...)
136 {
137 va_list ap;
138 private_data_t *priv = NULL;
139
140 pcmk__assert((out != NULL) && (out->priv != NULL));
141 priv = out->priv;
142
143 /* Error output does not get indented, to separate it from other
144 * potentially indented list output.
145 */
146 va_start(ap, format);
147 logger_va(priv, LOG_ERR, format, ap);
148 va_end(ap);
149 }
150
151 static void
152 log_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
153 xmlNodePtr node = NULL;
154 private_data_t *priv = NULL;
155
156 pcmk__assert((out != NULL) && (out->priv != NULL));
157 priv = out->priv;
158
159 node = pcmk__xe_create(NULL, name);
160 pcmk__xe_set_content(node, "%s", buf);
161 do_crm_log_xml(priv->log_level, name, node);
162 free(node);
163 }
164
165 G_GNUC_PRINTF(4, 5)
166 static void
167 log_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun,
168 const char *format, ...) {
169 int len = 0;
170 va_list ap;
171 char* buffer = NULL;
172 private_data_t *priv = NULL;
173
174 pcmk__assert((out != NULL) && (out->priv != NULL));
175 priv = out->priv;
176
177 va_start(ap, format);
178 len = vasprintf(&buffer, format, ap);
179 pcmk__assert(len >= 0);
180 va_end(ap);
181
182 /* Don't skip empty prefixes,
183 * otherwise there will be mismatch
184 * in the log_end_list */
185 if(strcmp(buffer, "") == 0) {
186 /* nothing */
187 }
188
189 g_queue_push_tail(priv->prefixes, buffer);
190 }
191
192 G_GNUC_PRINTF(3, 4)
193 static void
194 log_list_item(pcmk__output_t *out, const char *name, const char *format, ...)
195 {
196 gsize old_len = 0;
197 va_list ap;
198 private_data_t *priv = NULL;
199 GString *buffer = g_string_sized_new(128);
200
201 pcmk__assert((out != NULL) && (out->priv != NULL) && (format != NULL));
202 priv = out->priv;
203
204 // Message format: [<prefix1>[: <prefix2>...]: ]][<name>: ]<body>
205
206 for (const GList *iter = priv->prefixes->head; iter != NULL;
207 iter = iter->next) {
208
209 pcmk__g_strcat(buffer, (const char *) iter->data, ": ", NULL);
210 }
211
212 if (!pcmk__str_empty(name)) {
213 pcmk__g_strcat(buffer, name, ": ", NULL);
214 }
215
216 old_len = buffer->len;
217 va_start(ap, format);
218 g_string_append_vprintf(buffer, format, ap);
219 va_end(ap);
220
221 if (buffer->len > old_len) {
222 // Don't log a message with an empty body
223 logger(priv, "%s", buffer->str);
224 }
225
226 g_string_free(buffer, TRUE);
227 }
228
229 static void
230 log_end_list(pcmk__output_t *out) {
231 private_data_t *priv = NULL;
232
233 pcmk__assert((out != NULL) && (out->priv != NULL));
234 priv = out->priv;
235
236 if (priv->prefixes == NULL) {
237 return;
238 }
239 pcmk__assert(priv->prefixes->tail != NULL);
240
241 free((char *)priv->prefixes->tail->data);
242 g_queue_pop_tail(priv->prefixes);
243 }
244
245 G_GNUC_PRINTF(2, 3)
246 static int
247 log_info(pcmk__output_t *out, const char *format, ...)
248 {
249 va_list ap;
250 private_data_t *priv = NULL;
251
252 pcmk__assert((out != NULL) && (out->priv != NULL));
253 priv = out->priv;
254
255 /* Informational output does not get indented, to separate it from other
256 * potentially indented list output.
257 */
258 va_start(ap, format);
259 logger_va(priv, priv->log_level, format, ap);
260 va_end(ap);
261
262 return pcmk_rc_ok;
263 }
264
265 G_GNUC_PRINTF(2, 3)
266 static int
267 log_transient(pcmk__output_t *out, const char *format, ...)
268 {
269 va_list ap;
270 private_data_t *priv = NULL;
271
272 pcmk__assert((out != NULL) && (out->priv != NULL));
273 priv = out->priv;
274
275 va_start(ap, format);
276 logger_va(priv, QB_MAX(priv->log_level, LOG_DEBUG), format, ap);
277 va_end(ap);
278
279 return pcmk_rc_ok;
280 }
281
282 static bool
283 log_is_quiet(pcmk__output_t *out) {
284 return false;
285 }
286
287 static void
288 log_spacer(pcmk__output_t *out) {
289 /* This function intentionally left blank */
290 }
291
292 static void
293 log_progress(pcmk__output_t *out, bool end) {
294 /* This function intentionally left blank */
295 }
296
297 static void
298 log_prompt(const char *prompt, bool echo, char **dest) {
299 /* This function intentionally left blank */
300 }
301
302 pcmk__output_t *
303 pcmk__mk_log_output(char **argv) {
304 pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
305
306 if (retval == NULL) {
307 return NULL;
308 }
309
310 retval->fmt_name = "log";
311 retval->request = pcmk__quote_cmdline(argv);
312
313 retval->init = log_init;
314 retval->free_priv = log_free_priv;
315 retval->finish = log_finish;
316 retval->reset = log_reset;
317
318 retval->register_message = pcmk__register_message;
319 retval->message = pcmk__call_message;
320
321 retval->subprocess_output = log_subprocess_output;
322 retval->version = log_version;
323 retval->info = log_info;
324 retval->transient = log_transient;
325 retval->err = log_err;
326 retval->output_xml = log_output_xml;
327
328 retval->begin_list = log_begin_list;
329 retval->list_item = log_list_item;
330 retval->end_list = log_end_list;
331
332 retval->is_quiet = log_is_quiet;
333 retval->spacer = log_spacer;
334 retval->progress = log_progress;
335 retval->prompt = log_prompt;
336
337 return retval;
338 }
339
340 /*!
341 * \internal
342 * \brief Get the log level for a log output object
343 *
344 * This returns 0 if the output object is not of log format.
345 *
346 * \param[in] out Output object
347 *
348 * \return Current log level for \p out
349 */
350 uint8_t
351 pcmk__output_get_log_level(const pcmk__output_t *out)
352 {
353 pcmk__assert(out != NULL);
354
355 if (pcmk__str_eq(out->fmt_name, "log", pcmk__str_none)) {
356 private_data_t *priv = out->priv;
357
358 pcmk__assert(priv != NULL);
359 return priv->log_level;
360 }
361 return 0;
362 }
363
364 /*!
365 * \internal
366 * \brief Set the log level for a log output object
367 *
368 * This does nothing if the output object is not of log format.
369 *
370 * \param[in,out] out Output object
371 * \param[in] log_level Log level constant (\c LOG_ERR, etc.) to use
372 *
373 * \note \c LOG_INFO is used by default for new \c pcmk__output_t objects.
374 * \note Almost all formatted output messages respect this setting. However,
375 * <tt>out->err</tt> always logs at \c LOG_ERR.
376 */
377 void
378 pcmk__output_set_log_level(pcmk__output_t *out, uint8_t log_level)
379 {
380 pcmk__assert(out != NULL);
381
382 if (pcmk__str_eq(out->fmt_name, "log", pcmk__str_none)) {
383 private_data_t *priv = out->priv;
384
385 pcmk__assert(priv != NULL);
386 priv->log_level = log_level;
387 }
388 }
389
390 /*!
391 * \internal
392 * \brief Set the file, function, line, and tags used to filter log output
393 *
394 * This does nothing if the output object is not of log format.
395 *
396 * \param[in,out] out Output object
397 * \param[in] file File name to filter with (or NULL for default)
398 * \param[in] function Function name to filter with (or NULL for default)
399 * \param[in] line Line number to filter with (or 0 for default)
400 * \param[in] tags Tags to filter with (or 0 for none)
401 *
402 * \note Custom filters should generally be used only in short areas of a single
403 * function. When done, callers should call this function again with
404 * NULL/0 arguments to reset the filters.
405 */
406 void
407 pcmk__output_set_log_filter(pcmk__output_t *out, const char *file,
408 const char *function, uint32_t line, uint32_t tags)
409 {
410 pcmk__assert(out != NULL);
411
412 if (pcmk__str_eq(out->fmt_name, "log", pcmk__str_none)) {
413 private_data_t *priv = out->priv;
414
415 pcmk__assert(priv != NULL);
416 priv->file = file;
417 priv->function = function;
418 priv->line = line;
419 priv->tags = tags;
420 }
421 }
422