Coverage Report

Created: 2026-07-12 06:15

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