Coverage Report

Created: 2026-10-30 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pacemaker/lib/common/logging.c
Line
Count
Source
1
/*
2
 * Copyright 2004-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 <errno.h>                  // errno
13
#include <fcntl.h>                  // open, O_CREAT, O_RDWR, S_*
14
#include <libgen.h>                 // basename
15
#include <signal.h>                 // raise, SIG*
16
#include <stdbool.h>
17
#include <stddef.h>                 // NULL
18
#include <stdint.h>                 // int32_t, uint8_t, uint32_t
19
#include <stdlib.h>                 // free, getenv, strtol
20
#include <string.h>                 // strdup, strerror, strstr
21
#include <syslog.h>                 // LOG_*, syslog
22
#include <sys/stat.h>               // S_*, stat
23
#include <sys/types.h>              // gid_t, mode_t, pid_t, uid_t
24
#include <sys/utsname.h>            // uname, utsname
25
#include <time.h>                   // time, time_t, timespec
26
#include <unistd.h>                 // chdir, close, fchown, geteuid, getpid
27
28
#include <glib.h>                   // g_*, G_*, gboolean, gchar, etc.
29
#include <libxml/tree.h>            // xmlNode
30
#include <qb/qbdefs.h>              // qb_bit_set, QB_FALSE, QB_TRUE
31
#include <qb/qblog.h>               // LOG_TRACE, qb_log_*
32
33
#include <crm_config.h>             // CRM_DAEMON_USER, CRM_*_DIR
34
#include <crm/common/internal.h>    // pcmk__env_*, pcmk__output_*, etc.
35
#include <crm/common/logging.h>     // do_crm_log, CRM_CHECK, etc.
36
#include <crm/common/mainloop.h>    // crm_signal_handler, mainloop_add_signal
37
#include <crm/common/options.h>     // PCMK_VALUE_NONE
38
#include <crm/common/results.h>     // crm_abort, pcmk_rc_*
39
#include <crm/crm.h>                // crm_system_name
40
41
#include "crmcommon_private.h"      // pcmk__add_logfile
42
43
// Use high-resolution (millisecond) timestamps if libqb supports them
44
#ifdef QB_FEATURE_LOG_HIRES_TIMESTAMPS
45
0
#define TIMESTAMP_FORMAT_SPEC "%%T"
46
typedef struct timespec *log_time_t;
47
#else
48
#define TIMESTAMP_FORMAT_SPEC "%%t"
49
typedef time_t log_time_t;
50
#endif
51
52
unsigned int crm_log_level = LOG_INFO;
53
unsigned int crm_trace_nonlog = 0;
54
bool pcmk__is_daemon = false;
55
56
static int blackbox_trigger = 0;
57
static char *blackbox_file_prefix = NULL;
58
59
static gchar **trace_blackbox = NULL;
60
static gchar **trace_files = NULL;
61
static gchar **trace_formats = NULL;
62
static gchar **trace_functions = NULL;
63
static bool tracing_enabled = false;
64
65
static unsigned int crm_log_priority = LOG_NOTICE;
66
static pcmk__output_t *logger_out = NULL;
67
68
pcmk__config_error_func pcmk__config_error_handler = NULL;
69
pcmk__config_warning_func pcmk__config_warning_handler = NULL;
70
void *pcmk__config_error_context = NULL;
71
void *pcmk__config_warning_context = NULL;
72
73
/*!
74
 * \internal
75
 * \brief Mapping of a GLib log domain string to its log handler ID
76
 */
77
struct log_handler_id {
78
    //! Log domain
79
    const char *log_domain;
80
81
    /*!
82
     * Log handler function ID. GLib does not specify the meaning of 0, but
83
     * based on the implementation, a valid ID is always positive unless we set
84
     * UINT_MAX handlers.
85
     */
86
    unsigned int handler_id;
87
};
88
89
// Log domains that we care about, and their handler function IDs once set
90
static struct log_handler_id log_handler_ids[] = {
91
    { G_LOG_DOMAIN, 0 },
92
    { "GLib", 0 },
93
    { "GLib-GIO", 0 },
94
    { "GModule", 0 },
95
    { "GThread", 0 },
96
};
97
98
/*!
99
 * \internal
100
 * \brief Convert a GLib log level to a syslog log level
101
 *
102
 * \param[in] log_level  GLib log level
103
 *
104
 * \return The syslog level corresponding to \p log_level
105
 */
106
static uint8_t
107
log_level_from_glib(GLogLevelFlags log_level)
108
0
{
109
0
    switch (log_level & G_LOG_LEVEL_MASK) {
110
0
        case G_LOG_LEVEL_CRITICAL:
111
0
            return LOG_CRIT;
112
113
0
        case G_LOG_LEVEL_ERROR:
114
0
            return LOG_ERR;
115
116
0
        case G_LOG_LEVEL_MESSAGE:
117
0
            return LOG_NOTICE;
118
119
0
        case G_LOG_LEVEL_INFO:
120
0
            return LOG_INFO;
121
122
0
        case G_LOG_LEVEL_DEBUG:
123
0
            return LOG_DEBUG;
124
125
0
        case G_LOG_LEVEL_WARNING:
126
0
            return LOG_WARNING;
127
128
0
        default:
129
            // Default to LOG_NOTICE for any new or custom GLib log levels
130
0
            return LOG_NOTICE;
131
0
    }
132
0
}
133
134
/*!
135
 * \internal
136
 * \brief Handle a log message from GLib
137
 *
138
 * \param[in] log_domain  Log domain of the message
139
 * \param[in] log_level   Log level of the message (including fatal and
140
 *                        recursion flags)
141
 * \param[in] message     Message to process
142
 * \param[in] user_data   Ignored
143
 */
144
static void
145
handle_glib_message(const char *log_domain, GLogLevelFlags log_level,
146
                    const char *message, void *user_data)
147
0
{
148
0
    uint8_t syslog_level = log_level_from_glib(log_level);
149
150
0
    if (syslog_level == LOG_CRIT) {
151
0
        static struct qb_log_callsite *glib_cs = NULL;
152
153
0
        if (glib_cs == NULL) {
154
0
            glib_cs = qb_log_callsite_get(__func__, __FILE__, "glib-handler",
155
0
                                          LOG_DEBUG, __LINE__,
156
0
                                          crm_trace_nonlog);
157
0
        }
158
159
0
        if (!crm_is_callsite_active(glib_cs, LOG_DEBUG, crm_trace_nonlog)) {
160
            // Dump core
161
0
            crm_abort(__FILE__, __func__, __LINE__, message, true, true);
162
0
        }
163
0
    }
164
165
0
    do_crm_log(syslog_level, "%s: %s", log_domain, message);
166
0
}
167
168
/*!
169
 * \internal
170
 * \brief Set \c handle_glib_message() as the handler for each GLib log domain
171
 *
172
 * The handler will be set for all log levels, including fatal and recursive
173
 * messages, for each GLib log domain that we care about.
174
 */
175
static void
176
set_glib_log_handlers(void)
177
0
{
178
0
    for (int i = 0; i < G_N_ELEMENTS(log_handler_ids); i++) {
179
0
        struct log_handler_id *entry = &log_handler_ids[i];
180
181
0
        entry->handler_id = g_log_set_handler(entry->log_domain,
182
0
                                              G_LOG_LEVEL_MASK
183
0
                                              |G_LOG_FLAG_FATAL
184
0
                                              |G_LOG_FLAG_RECURSION,
185
0
                                              handle_glib_message, NULL);
186
0
    }
187
0
}
188
189
/*!
190
 * \internal
191
 * \brief Remove the handler for each GLib log domain that we care about
192
 */
193
static void
194
remove_glib_log_handlers(void)
195
0
{
196
0
    for (int i = 0; i < G_N_ELEMENTS(log_handler_ids); i++) {
197
0
        struct log_handler_id *entry = &log_handler_ids[i];
198
199
0
        if (entry->handler_id == 0) {
200
0
            continue;
201
0
        }
202
203
0
        g_log_remove_handler(entry->log_domain, entry->handler_id);
204
0
        entry->handler_id = 0;
205
0
    }
206
0
}
207
208
/*!
209
 * \internal
210
 * \brief Set the log format string based on the given target
211
 *
212
 * \param[in] target     Log target (detail level)
213
 * \param[in] pid        PID of current process
214
 * \param[in] node_name  Local node's name (for use in log output)
215
 */
216
static void
217
set_format_string(int target, pid_t pid, const char *node_name)
218
0
{
219
    /* Format specifiers used here:
220
     * - %b: message
221
     * - %f: file name
222
     * - %g: tag, or "" if none
223
     * - %l: line number
224
     * - %n: function name
225
     * - %p: priority (for example, "error" or "debug")
226
     * - %t: timestamp at seconds resolution
227
     * - %T: timestamp at milliseconds resolution
228
     */
229
0
    GString *fmt = NULL;
230
231
0
    if (target == QB_LOG_SYSLOG) {
232
        // The system log gets a simplified, user-friendly format
233
0
        qb_log_ctl(target, QB_LOG_CONF_EXTENDED, QB_FALSE);
234
0
        qb_log_format_set(target, "%g %p: %b");
235
0
        return;
236
0
    }
237
238
    // Everything else gets more detail, for advanced troubleshooting
239
0
    fmt = g_string_sized_new(256);
240
241
0
    if (target > QB_LOG_STDERR) {
242
        // If logging to a file, prefix with timestamp, node, system, and PID
243
0
        g_string_append_printf(fmt, TIMESTAMP_FORMAT_SPEC " %s %-20s[%lld] ",
244
0
                               node_name, crm_system_name, (long long) pid);
245
0
    }
246
247
    // Add function name (in parentheses)
248
0
    g_string_append(fmt, "(%n");
249
250
0
    if (tracing_enabled) {
251
        // When tracing, add file and line number
252
0
        g_string_append(fmt, "@%f:%l");
253
0
    }
254
255
0
    g_string_append_c(fmt, ')');
256
257
    // Add tag (if any), priority, and actual message
258
0
    g_string_append(fmt, " %g\t%p: %b");
259
260
0
    qb_log_format_set(target, fmt->str);
261
0
    g_string_free(fmt, TRUE);
262
0
}
263
264
0
#define DEFAULT_LOG_FILE CRM_LOG_DIR "/pacemaker.log"
265
266
/*!
267
 * \internal
268
 * \brief Ensure the daemon group owns the log file or has read/write access
269
 *
270
 * If the daemon group (\c haclient by default) doesn't own the log file or
271
 * can't read and write it, change the log file's ownership to the daemon user
272
 * and group.
273
 *
274
 * \param[in] filename  Log file name (for logging only)
275
 * \param[in] logfd     Log file descriptor
276
 *
277
 * \return Standard Pacemaker return code
278
 *
279
 * \todo Should we be less aggressive in changing ownership? The daemon group
280
 *       may have read/write permissions via file ACLs, or the daemon user may
281
 *       have permissions via user ownership, secondary group membership, or
282
 *       file ACLs. Also, even after changing group ownership, the daemon group
283
 *       may not have read/write permissions. Even if it does, we may unset them
284
 *       when applying PCMK__ENV_LOGFILE_MODE in \c chmod_logfile().
285
 */
286
static int
287
chown_logfile(const char *filename, int logfd)
288
0
{
289
0
    uid_t pcmk_uid = 0;
290
0
    gid_t pcmk_gid = 0;
291
0
    struct stat st;
292
0
    int rc = pcmk_rc_ok;
293
294
    // Get the log file's current ownership and permissions
295
0
    if (fstat(logfd, &st) < 0) {
296
0
        rc = errno;
297
0
        pcmk__warn("Logging to '%s' is disabled because fstat() failed: %s",
298
0
                   filename, strerror(rc));
299
0
        return rc;
300
0
    }
301
302
    // Any other errors don't prevent file from being used as log
303
304
0
    rc = pcmk__daemon_user(&pcmk_uid, &pcmk_gid);
305
0
    if (rc != pcmk_rc_ok) {
306
0
        pcmk__warn("Not changing '%s' ownership because user information "
307
0
                   "unavailable: %s",
308
0
                   filename, pcmk_rc_str(rc));
309
0
        return pcmk_rc_ok;
310
0
    }
311
312
0
    if ((st.st_gid == pcmk_gid)
313
0
        && ((st.st_mode & S_IRWXG) == (S_IRGRP|S_IWGRP))) {
314
315
        // Daemon group already owns the log file and has read/write permissions
316
0
        return pcmk_rc_ok;
317
0
    }
318
319
0
    if (fchown(logfd, pcmk_uid, pcmk_gid) < 0) {
320
0
        pcmk__warn("Couldn't change '%s' ownership to user %s gid %d: %s",
321
0
                   filename, CRM_DAEMON_USER, pcmk_gid, strerror(errno));
322
0
    }
323
324
0
    return pcmk_rc_ok;
325
0
}
326
327
/*!
328
 * \internal
329
 * \brief Set log file permissions using environment variable or default value
330
 *
331
 * If the \c PCMK__ENV_LOGFILE_MODE environment variable is set to a valid
332
 * value, this function sets the log file mode to that value. Otherwise, it sets
333
 * the log file mode to 0660.
334
 *
335
 * An error causes a warning to be logged but is otherwise ignored.
336
 *
337
 * \param[in] filename  Log file name (for logging only)
338
 * \param[in] logfd     Log file descriptor
339
 */
340
static void
341
chmod_logfile(const char *filename, int logfd)
342
0
{
343
0
    const char *modestr = pcmk__env_option(PCMK__ENV_LOGFILE_MODE);
344
0
    mode_t filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
345
346
0
    errno = 0;
347
0
    if (modestr != NULL) {
348
0
        char *end = NULL;
349
0
        const long filemode_l = strtol(modestr, &end, 8);
350
0
        const mode_t mask = S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO;
351
352
0
        if ((errno != 0) || (end == modestr) || !pcmk__str_empty(end)
353
0
            || (((mode_t) filemode_l & ~mask) != 0)) {
354
355
0
            pcmk__warn("Setting '%s' mode to %04o because "
356
0
                       "PCMK_" PCMK__ENV_LOGFILE_MODE " '%s' is invalid",
357
0
                       filename, filemode, modestr);
358
359
0
        } else {
360
0
            filemode = (mode_t) filemode_l;
361
0
        }
362
0
    }
363
364
0
    if (fchmod(logfd, filemode) < 0) {
365
0
        pcmk__warn("Couldn't change '%s' mode to %04o: %s", filename, filemode,
366
0
                   strerror(errno));
367
0
    }
368
0
}
369
370
/*!
371
 * \internal
372
 * \brief If we're root, ensure a log file has correct permissions and ownership
373
 *
374
 * \param[in] filename  Log file name
375
 *
376
 * \return Standard Pacemaker return code
377
 */
378
static int
379
set_logfile_permissions(const char *filename)
380
0
{
381
0
    int fd = 0;
382
0
    int rc = pcmk_rc_ok;
383
384
0
    if (geteuid() != 0) {
385
0
        goto done;
386
0
    }
387
388
0
    fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
389
0
    if (fd < 0) {
390
0
        rc = errno;
391
0
        pcmk__warn("Logging to '%s' is disabled because open() failed as root: "
392
0
                   "%s", filename, strerror(rc));
393
0
        goto done;
394
0
    }
395
396
0
    rc = chown_logfile(filename, fd);
397
0
    if (rc != pcmk_rc_ok) {
398
0
        goto done;
399
0
    }
400
401
0
    chmod_logfile(filename, fd);
402
403
0
done:
404
0
    if (fd >= 0) {
405
0
        close(fd);
406
0
    }
407
0
    return rc;
408
0
}
409
410
/*!
411
 * \internal
412
 * \brief Enable a libqb log target, extend max line length, and apply filter
413
 *
414
 * \param[in] target  Log target (either an <tt>enum qb_log_target_slot</tt> or
415
 *                    the return value of a \c qb_log_file_open() call)
416
 */
417
static void
418
enable_logfile(int32_t target)
419
0
{
420
0
    qb_log_ctl(target, QB_LOG_CONF_ENABLED, QB_TRUE);
421
422
0
#ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN
423
    // Longer than default, for logging long XML lines
424
0
    qb_log_ctl(target, QB_LOG_CONF_MAX_LINE_LEN, 800);
425
0
#endif
426
427
0
    crm_update_callsites();
428
0
}
429
430
/*!
431
 * \internal
432
 * \brief Disable a libqb log target
433
 *
434
 * \param[in] target  Log target (either an <tt>enum qb_log_target_slot</tt> or
435
 *                    the return value of a \c qb_log_file_open() call)
436
 */
437
static inline void
438
disable_logfile(int32_t target)
439
0
{
440
0
    qb_log_ctl(target, QB_LOG_CONF_ENABLED, QB_FALSE);
441
0
}
442
443
static void
444
setenv_logfile(const char *filename)
445
0
{
446
    // Some resource agents will log only if environment variable is set
447
0
    if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) {
448
0
        pcmk__set_env_option(PCMK__ENV_LOGFILE, filename, true);
449
0
    }
450
0
}
451
452
/*!
453
 * \internal
454
 * \brief Add a file to be used as a Pacemaker detail log
455
 *
456
 * \param[in] filename  Name of log file to use. \c NULL adds the default log
457
 *                      file if no log file is enabled yet. \c PCMK_VALUE_NONE
458
 *                      or \c "/dev/null" causes this function to do nothing.
459
 *
460
 * \return Standard Pacemaker return code
461
 *
462
 * \note Messages from this function won't be logged to the new log file.
463
 */
464
int
465
pcmk__add_logfile(const char *filename)
466
0
{
467
0
    static int32_t default_target = -1;
468
0
    static bool have_logfile = false;
469
470
0
    int32_t target = 0;
471
0
    int rc = pcmk_rc_ok;
472
0
    bool is_default = false;
473
474
    // No logging to do if the file is "/dev/null" or special value "none"
475
0
    if (pcmk__str_eq(filename, "/dev/null", pcmk__str_none)
476
0
        || pcmk__str_eq(filename, PCMK_VALUE_NONE, pcmk__str_casei)) {
477
478
0
        return pcmk_rc_ok;
479
0
    }
480
481
    // Use default if caller didn't specify (and we don't already have one)
482
0
    if (filename == NULL) {
483
0
        if (have_logfile) {
484
0
            return pcmk_rc_ok;
485
0
        }
486
0
        filename = DEFAULT_LOG_FILE;
487
0
    }
488
489
    // If the caller wants the default and we already have it, we're done
490
0
    is_default = pcmk__str_eq(filename, DEFAULT_LOG_FILE, pcmk__str_none);
491
0
    if (is_default && (default_target >= 0)) {
492
0
        return pcmk_rc_ok;
493
0
    }
494
495
    // If we're root, ensure ownership and permissions are correct
496
0
    rc = set_logfile_permissions(filename);
497
0
    if (rc != pcmk_rc_ok) {
498
        // Warning has already been logged
499
0
        return rc;
500
0
    }
501
502
    // Open as a libqb log file
503
0
    target = qb_log_file_open(filename);
504
0
    if (target < 0) {
505
0
        rc = -target;   // target == -errno
506
0
        pcmk__warn("Logging to '%s' is disabled because qb_log_file_open() "
507
0
                   "failed: %s " QB_XS " uid=%lld gid=%lld",
508
0
                   filename, strerror(rc), (long long) geteuid(),
509
0
                   (long long) getegid());
510
0
        return rc;
511
0
    }
512
513
0
    if (is_default) {
514
0
        default_target = target;
515
0
        setenv_logfile(filename);
516
517
0
    } else if (default_target >= 0) {
518
        /* @TODO Why do we disable logging to the default log file when adding
519
         * another log file? This seems wrong, especially if the default file is
520
         * configured explicitly.
521
         */
522
0
        pcmk__notice("Switching logging to %s", filename);
523
0
        disable_logfile(default_target);
524
0
    }
525
526
0
    pcmk__notice("Additional logging available in %s", filename);
527
0
    enable_logfile(target);
528
0
    have_logfile = true;
529
0
    return pcmk_rc_ok;
530
0
}
531
532
/*!
533
 * \brief Add multiple additional log files
534
 *
535
 * \param[in] log_files  Array of log files to add
536
 * \param[in] out        Output object to use for error reporting
537
 *
538
 * \return Standard Pacemaker return code
539
 */
540
void
541
pcmk__add_logfiles(char **log_files, pcmk__output_t *out)
542
0
{
543
0
    if (log_files == NULL) {
544
0
        return;
545
0
    }
546
547
0
    for (char **fname = log_files; *fname != NULL; fname++) {
548
0
        int rc = pcmk__add_logfile(*fname);
549
550
0
        if (rc != pcmk_rc_ok) {
551
0
            out->err(out, "Logging to %s is disabled: %s",
552
0
                     *fname, pcmk_rc_str(rc));
553
0
        }
554
0
    }
555
0
}
556
557
/*!
558
 * \internal
559
 * \brief Write out a blackbox (enabling blackboxes if the signal is \c SIGTRAP)
560
 *
561
 * \param[in] nsig  Signal number that was received
562
 *
563
 * \note This is a true signal handler, and so must be async-safe.
564
 */
565
static void
566
enable_and_write_blackbox(int nsig)
567
0
{
568
0
    if (nsig == SIGTRAP) {
569
0
        crm_enable_blackbox(nsig);
570
0
    }
571
572
0
    crm_write_blackbox(nsig, NULL);
573
0
}
574
575
static void
576
blackbox_logger(int32_t t, struct qb_log_callsite *cs, log_time_t timestamp,
577
                const char *msg)
578
0
{
579
0
    if(cs && cs->priority < LOG_ERR) {
580
0
        crm_write_blackbox(SIGTRAP, cs); /* Bypass the over-dumping logic */
581
0
    } else {
582
0
        crm_write_blackbox(0, cs);
583
0
    }
584
0
}
585
586
void
587
crm_enable_blackbox(int nsig)
588
0
{
589
0
    if (qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET,
590
0
                   0) == QB_LOG_STATE_ENABLED) {
591
0
        return;
592
0
    }
593
594
    // Any size change drops existing entries
595
0
    qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_SIZE, 5 * 1024 * 1024);
596
597
    // Setting the size seems to disable the log target
598
0
    qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
599
600
    /* Enable synchronous logging for each target except QB_LOG_SYSLOG and
601
     * QB_LOG_STDERR
602
     */
603
0
    for (int i = QB_LOG_BLACKBOX; i < QB_LOG_TARGET_MAX; i++) {
604
0
        qb_log_ctl(i, QB_LOG_CONF_FILE_SYNC, QB_TRUE);
605
0
    }
606
607
0
    pcmk__notice("Initiated blackbox recorder: %s", blackbox_file_prefix);
608
609
    // Save to disk on abnormal termination
610
0
    crm_signal_handler(SIGSEGV, enable_and_write_blackbox);
611
0
    crm_signal_handler(SIGABRT, enable_and_write_blackbox);
612
0
    crm_signal_handler(SIGILL, enable_and_write_blackbox);
613
0
    crm_signal_handler(SIGBUS, enable_and_write_blackbox);
614
0
    crm_signal_handler(SIGFPE, enable_and_write_blackbox);
615
616
0
    crm_update_callsites();
617
618
0
    blackbox_trigger = qb_log_custom_open(blackbox_logger, NULL, NULL, NULL);
619
0
    qb_log_ctl(blackbox_trigger, QB_LOG_CONF_ENABLED, QB_TRUE);
620
0
    pcmk__trace("Trigger: %d is %d %d", blackbox_trigger,
621
0
                qb_log_ctl(blackbox_trigger, QB_LOG_CONF_STATE_GET, 0),
622
0
                QB_LOG_STATE_ENABLED);
623
624
0
    crm_update_callsites();
625
0
}
626
627
void
628
crm_disable_blackbox(int nsig)
629
0
{
630
0
    if (qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET,
631
0
                   0) != QB_LOG_STATE_ENABLED) {
632
0
        return;
633
0
    }
634
635
0
    qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
636
637
    // Disable synchronous logging again when the blackbox is disabled
638
0
    for (int i = QB_LOG_BLACKBOX; i < QB_LOG_TARGET_MAX; i++) {
639
0
        qb_log_ctl(i, QB_LOG_CONF_FILE_SYNC, QB_FALSE);
640
0
    }
641
0
}
642
643
/*!
644
 * \internal
645
 * \brief Write out a blackbox, if blackboxes are enabled
646
 *
647
 * \param[in] nsig  Signal that was received
648
 * \param[in] cs    libqb callsite
649
 *
650
 * \note This may be called via a true signal handler and so must be async-safe.
651
 * @TODO actually make this async-safe
652
 */
653
void
654
crm_write_blackbox(int nsig, const struct qb_log_callsite *cs)
655
0
{
656
0
    static volatile int counter = 1;
657
0
    static volatile time_t last = 0;
658
659
0
    char *buffer = NULL;
660
0
    int rc = 0;
661
0
    time_t now = time(NULL);
662
663
0
    if (blackbox_file_prefix == NULL) {
664
0
        return;
665
0
    }
666
667
0
    switch (nsig) {
668
0
        case 0:
669
0
        case SIGTRAP:
670
            /* The graceful case - such as assertion failure or user request */
671
672
0
            if (nsig == 0 && now == last) {
673
                /* Prevent over-dumping */
674
0
                return;
675
0
            }
676
677
0
            buffer = pcmk__assert_asprintf("%s.%d", blackbox_file_prefix,
678
0
                                           counter++);
679
0
            if (nsig == SIGTRAP) {
680
0
                pcmk__notice("Blackbox dump requested, please see %s for "
681
0
                             "contents",
682
0
                             buffer);
683
684
0
            } else if (cs) {
685
0
                syslog(LOG_NOTICE,
686
0
                       "Problem detected at %s:%d (%s), please see %s for additional details",
687
0
                       cs->function, cs->lineno, cs->filename, buffer);
688
0
            } else {
689
0
                pcmk__notice("Problem detected, please see %s for additional "
690
0
                             "details",
691
0
                             buffer);
692
0
            }
693
694
0
            last = now;
695
696
0
            rc = qb_log_blackbox_write_to_file(buffer);
697
0
            if (rc < 0) {
698
                // System errno
699
0
                pcmk__err("Failed to write blackbox file %s: %s", buffer,
700
0
                          strerror(-rc));
701
0
            }
702
703
            /* Flush the existing contents
704
             * A size change would also work
705
             */
706
0
            qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
707
0
            qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
708
0
            break;
709
710
0
        default:
711
            /* Do as little as possible, just try to get what we have out
712
             * We logged the filename when the blackbox was enabled
713
             */
714
0
            crm_signal_handler(nsig, SIG_DFL);
715
0
            qb_log_blackbox_write_to_file((const char *)blackbox_file_prefix);
716
0
            qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
717
0
            raise(nsig);
718
0
            break;
719
0
    }
720
721
0
    free(buffer);
722
0
}
723
724
static const char *
725
crm_quark_to_string(uint32_t tag)
726
0
{
727
0
    const char *text = g_quark_to_string(tag);
728
729
0
    if (text) {
730
0
        return text;
731
0
    }
732
0
    return "";
733
0
}
734
735
static void
736
crm_log_filter_source(int source, struct qb_log_callsite *cs)
737
0
{
738
0
    if (qb_log_ctl(source, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
739
0
        return;
740
0
    }
741
742
0
    if ((cs->tags != crm_trace_nonlog) && (source == QB_LOG_BLACKBOX)) {
743
        /* Blackbox gets everything if enabled */
744
0
        qb_bit_set(cs->targets, source);
745
0
        return;
746
0
    }
747
748
0
    if ((source == blackbox_trigger) && (blackbox_trigger > 0)) {
749
        /* Should this log message result in the blackbox being dumped */
750
0
        if (cs->priority <= LOG_ERR) {
751
0
            qb_bit_set(cs->targets, source);
752
753
0
        } else if (trace_blackbox != NULL) {
754
0
            char *key = pcmk__assert_asprintf("%s:%d", cs->function,
755
0
                                              cs->lineno);
756
757
0
            if (pcmk__g_strv_contains(trace_blackbox, key)) {
758
0
                qb_bit_set(cs->targets, source);
759
0
            }
760
0
            free(key);
761
0
        }
762
0
        return;
763
0
    }
764
765
0
    if (source == QB_LOG_SYSLOG) {
766
        // No tracing to syslog
767
0
        if ((cs->priority <= crm_log_priority)
768
0
            && (cs->priority <= crm_log_level)) {
769
770
0
            qb_bit_set(cs->targets, source);
771
0
        }
772
0
        return;
773
0
    }
774
775
0
    if (cs->priority <= crm_log_level) {
776
0
        qb_bit_set(cs->targets, source);
777
0
        return;
778
0
    }
779
780
0
    if ((trace_files != NULL)
781
0
        && pcmk__g_strv_contains(trace_files, cs->filename)) {
782
783
0
        qb_bit_set(cs->targets, source);
784
0
        return;
785
0
    }
786
787
0
    if ((trace_functions != NULL)
788
0
        && pcmk__g_strv_contains(trace_functions, cs->function)) {
789
790
0
        qb_bit_set(cs->targets, source);
791
0
        return;
792
0
    }
793
794
0
    if ((trace_formats != NULL)
795
0
        && pcmk__g_strv_contains(trace_formats, cs->format)) {
796
797
0
        qb_bit_set(cs->targets, source);
798
0
        return;
799
0
    }
800
801
0
    if ((cs->tags != 0) && (cs->tags != crm_trace_nonlog)
802
0
        && (g_quark_to_string(cs->tags) != NULL)) {
803
804
0
        qb_bit_set(cs->targets, source);
805
0
    }
806
0
}
807
808
static void
809
crm_log_filter(struct qb_log_callsite *cs)
810
0
{
811
0
    cs->targets = 0;            /* Reset then find targets to enable */
812
0
    for (int i = QB_LOG_SYSLOG; i < QB_LOG_TARGET_MAX; i++) {
813
0
        crm_log_filter_source(i, cs);
814
0
    }
815
0
}
816
817
/*!
818
 * \internal
819
 * \brief Parse environment variables specifying which objects to trace
820
 */
821
static void
822
init_tracing(void)
823
0
{
824
0
    const char *blackbox = pcmk__env_option(PCMK__ENV_TRACE_BLACKBOX);
825
0
    const char *files = pcmk__env_option(PCMK__ENV_TRACE_FILES);
826
0
    const char *formats = pcmk__env_option(PCMK__ENV_TRACE_FORMATS);
827
0
    const char *functions = pcmk__env_option(PCMK__ENV_TRACE_FUNCTIONS);
828
0
    const char *tags = pcmk__env_option(PCMK__ENV_TRACE_TAGS);
829
830
0
    if (blackbox != NULL) {
831
0
        trace_blackbox = g_strsplit(blackbox, ",", 0);
832
0
    }
833
834
0
    if (files != NULL) {
835
0
        trace_files = g_strsplit(files, ",", 0);
836
0
        tracing_enabled = true;
837
0
    }
838
839
0
    if (formats != NULL) {
840
0
        trace_formats = g_strsplit(formats, ",", 0);
841
0
        tracing_enabled = true;
842
0
    }
843
844
0
    if (functions != NULL) {
845
0
        trace_functions = g_strsplit(functions, ",", 0);
846
0
        tracing_enabled = true;
847
0
    }
848
849
0
    if (tags != NULL) {
850
0
        gchar **trace_tags = g_strsplit(tags, ",", 0);
851
852
0
        for (gchar **tag = trace_tags; *tag != NULL; tag++) {
853
0
            if (pcmk__str_empty(*tag)) {
854
0
                continue;
855
0
            }
856
857
0
            pcmk__info("Created GQuark %lld from token '%s' in '%s'",
858
0
                       (long long) g_quark_from_string(*tag), *tag, tags);
859
0
            tracing_enabled = true;
860
0
        }
861
862
        // We have the GQuarks, so we don't need the array anymore
863
0
        g_strfreev(trace_tags);
864
0
    }
865
0
}
866
867
/*!
868
 * \internal
869
 * \brief Free arrays of parsed trace objects
870
 *
871
 * \note GQuarks for trace tags can't be removed.
872
 */
873
static void
874
cleanup_tracing(void)
875
0
{
876
0
    g_clear_pointer(&trace_blackbox, g_strfreev);
877
0
    g_clear_pointer(&trace_files, g_strfreev);
878
0
    g_clear_pointer(&trace_formats, g_strfreev);
879
0
    g_clear_pointer(&trace_functions, g_strfreev);
880
0
    tracing_enabled = false;
881
0
}
882
883
gboolean
884
crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags)
885
114k
{
886
114k
    gboolean refilter = FALSE;
887
888
114k
    if (cs == NULL) {
889
114k
        return FALSE;
890
114k
    }
891
892
0
    if (cs->priority != level) {
893
0
        cs->priority = level;
894
0
        refilter = TRUE;
895
0
    }
896
897
0
    if (cs->tags != tags) {
898
0
        cs->tags = tags;
899
0
        refilter = TRUE;
900
0
    }
901
902
0
    if (refilter) {
903
0
        crm_log_filter(cs);
904
0
    }
905
906
0
    if (cs->targets == 0) {
907
0
        return FALSE;
908
0
    }
909
0
    return TRUE;
910
0
}
911
912
void
913
crm_update_callsites(void)
914
3.72k
{
915
3.72k
    static bool log = true;
916
917
3.72k
    if (log) {
918
1
        log = false;
919
1
        pcmk__debug("Enabling callsites based on priority=%d, files=%s, "
920
1
                    "functions=%s, formats=%s, tags=%s",
921
1
                    crm_log_level,
922
0
                    pcmk__s(pcmk__env_option(PCMK__ENV_TRACE_FILES), "<null>"),
923
0
                    pcmk__s(pcmk__env_option(PCMK__ENV_TRACE_FUNCTIONS),
924
0
                            "<null>"),
925
0
                    pcmk__s(pcmk__env_option(PCMK__ENV_TRACE_FORMATS),
926
0
                            "<null>"),
927
0
                    pcmk__s(pcmk__env_option(PCMK__ENV_TRACE_TAGS), "<null>"));
928
1
    }
929
3.72k
    qb_log_filter_fn_set(crm_log_filter);
930
3.72k
}
931
932
static int
933
crm_priority2int(const char *name)
934
0
{
935
0
    struct syslog_names {
936
0
        const char *name;
937
0
        int priority;
938
0
    };
939
0
    static struct syslog_names p_names[] = {
940
0
        {"emerg", LOG_EMERG},
941
0
        {"alert", LOG_ALERT},
942
0
        {"crit", LOG_CRIT},
943
0
        {"error", LOG_ERR},
944
0
        {"warning", LOG_WARNING},
945
0
        {"notice", LOG_NOTICE},
946
0
        {"info", LOG_INFO},
947
0
        {"debug", LOG_DEBUG},
948
0
        {NULL, -1}
949
0
    };
950
951
0
    for (int i = 0; (name != NULL) && (p_names[i].name != NULL); i++) {
952
0
        if (pcmk__str_eq(p_names[i].name, name, pcmk__str_none)) {
953
0
            return p_names[i].priority;
954
0
        }
955
0
    }
956
0
    return crm_log_priority;
957
0
}
958
959
960
/*!
961
 * \internal
962
 * \brief Set the identifier for the current process
963
 *
964
 * If the identifier crm_system_name is not already set, then it is set as follows:
965
 * - it is passed to the function via the "entity" parameter, or
966
 * - it is derived from the executable name
967
 *
968
 * The identifier can be used in logs, IPC, and more.
969
 *
970
 * This method also sets the PCMK_service environment variable.
971
 *
972
 * \param[in] entity  If not NULL, will be assigned to the identifier
973
 * \param[in] argc    The number of command line parameters
974
 * \param[in] argv    The command line parameter values
975
 */
976
static void
977
set_identity(const char *entity, int argc, char *const *argv)
978
0
{
979
0
    if (crm_system_name != NULL) {
980
0
        return; // Already set, don't overwrite
981
0
    }
982
983
0
    if (entity != NULL) {
984
0
        crm_system_name = pcmk__str_copy(entity);
985
986
0
    } else if ((argc > 0) && (argv != NULL)) {
987
0
        char *mutable = strdup(argv[0]);
988
0
        char *modified = basename(mutable);
989
990
0
        if (strstr(modified, "lt-") == modified) {
991
0
            modified += 3;
992
0
        }
993
0
        crm_system_name = pcmk__str_copy(modified);
994
0
        free(mutable);
995
996
0
    } else {
997
0
        crm_system_name = pcmk__str_copy("Unknown");
998
0
    }
999
1000
    // Used by fencing.py.py (in fence-agents)
1001
0
    pcmk__set_env_option(PCMK__ENV_SERVICE, crm_system_name, false);
1002
0
}
1003
1004
void
1005
crm_log_preinit(const char *entity, int argc, char *const *argv)
1006
0
{
1007
    /* Configure libqb logging with nothing turned on */
1008
0
    static bool have_logging = false;
1009
0
    struct utsname res = { 0, };
1010
0
    const pid_t pid = getpid();
1011
0
    const char *nodename = "localhost";
1012
1013
0
    if (have_logging) {
1014
0
        return;
1015
0
    }
1016
1017
0
    have_logging = true;
1018
1019
0
    init_tracing();
1020
1021
    /* @TODO Try to create a more obvious "global Pacemaker initializer"
1022
     * function than crm_log_preinit(), and call pcmk__schema_init() there.
1023
     * See also https://projects.clusterlabs.org/T840.
1024
     */
1025
0
    pcmk__schema_init();
1026
1027
0
    if (crm_trace_nonlog == 0) {
1028
0
        crm_trace_nonlog = g_quark_from_static_string("Pacemaker non-logging tracepoint");
1029
0
    }
1030
1031
0
    umask(S_IWGRP | S_IWOTH | S_IROTH);
1032
1033
0
    set_glib_log_handlers();
1034
1035
    /* glib should not abort for any messages from the Pacemaker domain, but
1036
     * other domains are still free to specify their own behavior.  However,
1037
     * note that G_LOG_LEVEL_ERROR is always fatal regardless of what we do
1038
     * here.
1039
     */
1040
0
    g_log_set_fatal_mask(G_LOG_DOMAIN, 0);
1041
1042
    /* Set crm_system_name, which is used as the logging name. It may also
1043
     * be used for other purposes such as an IPC client name.
1044
     */
1045
0
    set_identity(entity, argc, argv);
1046
1047
0
    qb_log_init(crm_system_name, qb_log_facility2int("local0"), LOG_ERR);
1048
0
    crm_log_level = LOG_CRIT;
1049
1050
    /* Nuke any syslog activity until it's asked for */
1051
0
    qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE);
1052
0
#ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN
1053
    // Shorter than default, generous for what we *should* send to syslog
1054
0
    qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_MAX_LINE_LEN, 256);
1055
0
#endif
1056
1057
0
    if ((uname(&res) == 0) && !pcmk__str_empty(res.nodename)) {
1058
0
        nodename = res.nodename;
1059
0
    }
1060
1061
    /* Set format strings and disable threading
1062
     * Pacemaker and threads do not mix well (due to the amount of forking)
1063
     */
1064
0
    qb_log_tags_stringify_fn_set(crm_quark_to_string);
1065
0
    for (int i = QB_LOG_SYSLOG; i < QB_LOG_TARGET_MAX; i++) {
1066
0
        qb_log_ctl(i, QB_LOG_CONF_THREADED, QB_FALSE);
1067
0
#ifdef HAVE_qb_log_conf_QB_LOG_CONF_ELLIPSIS
1068
        // End truncated lines with '...'
1069
0
        qb_log_ctl(i, QB_LOG_CONF_ELLIPSIS, QB_TRUE);
1070
0
#endif
1071
0
        set_format_string(i, pid, nodename);
1072
0
    }
1073
1074
#ifdef ENABLE_NLS
1075
    /* Enable translations (experimental). Currently we only have a few
1076
     * proof-of-concept translations for some option help. The goal would be to
1077
     * offer translations for option help and man pages rather than logs or
1078
     * documentation, to reduce the burden of maintaining them.
1079
     */
1080
1081
    // Load locale information for the local host from the environment
1082
    setlocale(LC_ALL, "");
1083
1084
    // Tell gettext where to find Pacemaker message catalogs
1085
    pcmk__assert(bindtextdomain(PACKAGE, PCMK__LOCALE_DIR) != NULL);
1086
1087
    // Tell gettext to use the Pacemaker message catalogs
1088
    pcmk__assert(textdomain(PACKAGE) != NULL);
1089
1090
    // Tell gettext that the translated strings are stored in UTF-8
1091
    bind_textdomain_codeset(PACKAGE, "UTF-8");
1092
#endif
1093
0
}
1094
1095
gboolean
1096
crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_stderr,
1097
             int argc, char **argv, gboolean quiet)
1098
0
{
1099
0
    const char *syslog_priority = NULL;
1100
0
    const char *facility = pcmk__env_option(PCMK__ENV_LOGFACILITY);
1101
0
    const char *f_copy = facility;
1102
1103
0
    pcmk__is_daemon = daemon;
1104
0
    crm_log_preinit(entity, argc, argv);
1105
1106
0
    if (level > LOG_TRACE) {
1107
0
        level = LOG_TRACE;
1108
0
    }
1109
0
    if(level > crm_log_level) {
1110
0
        crm_log_level = level;
1111
0
    }
1112
1113
    /* Should we log to syslog */
1114
0
    if (facility == NULL) {
1115
0
        if (pcmk__is_daemon) {
1116
0
            facility = "daemon";
1117
0
        } else {
1118
0
            facility = PCMK_VALUE_NONE;
1119
0
        }
1120
0
        pcmk__set_env_option(PCMK__ENV_LOGFACILITY, facility, true);
1121
0
    }
1122
1123
0
    if (pcmk__str_eq(facility, PCMK_VALUE_NONE, pcmk__str_casei)) {
1124
0
        quiet = TRUE;
1125
1126
1127
0
    } else {
1128
0
        qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_FACILITY, qb_log_facility2int(facility));
1129
0
    }
1130
1131
0
    if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_DEBUG)) {
1132
        /* Override the default setting */
1133
0
        crm_log_level = LOG_DEBUG;
1134
0
    }
1135
1136
    /* What lower threshold do we have for sending to syslog */
1137
0
    syslog_priority = pcmk__env_option(PCMK__ENV_LOGPRIORITY);
1138
0
    if (syslog_priority) {
1139
0
        crm_log_priority = crm_priority2int(syslog_priority);
1140
0
    }
1141
0
    qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*",
1142
0
                      crm_log_priority);
1143
1144
    // Log to syslog unless requested to be quiet
1145
0
    if (!quiet) {
1146
0
        qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_TRUE);
1147
0
    }
1148
1149
    /* Should we log to stderr */ 
1150
0
    if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_STDERR)) {
1151
        /* Override the default setting */
1152
0
        to_stderr = TRUE;
1153
0
    }
1154
0
    crm_enable_stderr(to_stderr);
1155
1156
    // Log to a file if we're a daemon or user asked for one
1157
0
    {
1158
0
        const char *logfile = pcmk__env_option(PCMK__ENV_LOGFILE);
1159
1160
0
        if (pcmk__is_daemon || (logfile != NULL)) {
1161
            // Daemons always get a log file, unless explicitly set to "none"
1162
0
            pcmk__add_logfile(logfile);
1163
0
        }
1164
0
    }
1165
1166
0
    if (pcmk__is_daemon
1167
0
        && pcmk__env_option_enabled(crm_system_name, PCMK__ENV_BLACKBOX)) {
1168
0
        crm_enable_blackbox(0);
1169
0
    }
1170
1171
    /* Summary */
1172
0
    pcmk__trace("Quiet: %d, facility %s", quiet, f_copy);
1173
0
    pcmk__env_option(PCMK__ENV_LOGFILE);
1174
0
    pcmk__env_option(PCMK__ENV_LOGFACILITY);
1175
1176
0
    crm_update_callsites();
1177
1178
    /* Ok, now we can start logging... */
1179
1180
    // Disable daemon request if user isn't root or Pacemaker daemon user
1181
0
    if (pcmk__is_daemon) {
1182
0
        const char *user = getenv("USER");
1183
1184
0
        if (user != NULL && !pcmk__strcase_any_of(user, "root", CRM_DAEMON_USER, NULL)) {
1185
0
            pcmk__trace("Not switching to corefile directory for %s", user);
1186
0
            pcmk__is_daemon = false;
1187
0
        }
1188
0
    }
1189
1190
0
    if (pcmk__is_daemon) {
1191
0
        char *user = pcmk__uid2username(getuid());
1192
1193
0
        if (user == NULL) {
1194
            // Error already logged
1195
1196
0
        } else if (!pcmk__str_any_of(user, "root", CRM_DAEMON_USER, NULL)) {
1197
0
            pcmk__trace("Don't change active directory for regular user %s",
1198
0
                        user);
1199
1200
0
        } else if (chdir(CRM_CORE_DIR) < 0) {
1201
0
            pcmk__info("Cannot change active directory to " CRM_CORE_DIR ": %s",
1202
0
                       strerror(errno));
1203
1204
0
        } else {
1205
0
            pcmk__info("Changed active directory to " CRM_CORE_DIR);
1206
0
        }
1207
1208
0
        blackbox_file_prefix = pcmk__assert_asprintf(CRM_BLACKBOX_DIR
1209
0
                                                     "/%s-%lld",
1210
0
                                                     crm_system_name,
1211
0
                                                     (long long) getpid());
1212
        /* Original meanings from signal(7)
1213
         *
1214
         * Signal       Value     Action   Comment
1215
         * SIGTRAP        5        Core    Trace/breakpoint trap
1216
         * SIGUSR1     30,10,16    Term    User-defined signal 1
1217
         * SIGUSR2     31,12,17    Term    User-defined signal 2
1218
         *
1219
         * Our usage is as similar as possible
1220
         */
1221
0
        mainloop_add_signal(SIGUSR1, crm_enable_blackbox);
1222
0
        mainloop_add_signal(SIGUSR2, crm_disable_blackbox);
1223
0
        mainloop_add_signal(SIGTRAP, enable_and_write_blackbox);
1224
1225
0
        free(user);
1226
1227
0
    } else if (!quiet) {
1228
0
        crm_log_args(argc, argv);
1229
0
    }
1230
1231
0
    return TRUE;
1232
0
}
1233
1234
/*!
1235
 * \internal
1236
 * \brief Free the logging library's internal data structures
1237
 */
1238
void
1239
crm_log_deinit(void)
1240
0
{
1241
0
    remove_glib_log_handlers();
1242
0
    cleanup_tracing();
1243
1244
0
    if (logger_out != NULL) {
1245
0
        logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
1246
0
        g_clear_pointer(&logger_out, pcmk__output_free);
1247
0
    }
1248
1249
0
    g_clear_pointer(&blackbox_file_prefix, free);
1250
0
    g_clear_pointer(&crm_system_name, free);
1251
0
}
1252
1253
/* returns the old value */
1254
unsigned int
1255
set_crm_log_level(unsigned int level)
1256
0
{
1257
0
    unsigned int old = crm_log_level;
1258
1259
0
    if (level > LOG_TRACE) {
1260
0
        level = LOG_TRACE;
1261
0
    }
1262
0
    crm_log_level = level;
1263
0
    crm_update_callsites();
1264
0
    pcmk__trace("New log level: %d", level);
1265
0
    return old;
1266
0
}
1267
1268
void
1269
crm_enable_stderr(int enable)
1270
3.72k
{
1271
3.72k
    if (enable && qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
1272
3.72k
        qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE);
1273
3.72k
        crm_update_callsites();
1274
1275
3.72k
    } else if (enable == FALSE) {
1276
0
        qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_FALSE);
1277
0
    }
1278
3.72k
}
1279
1280
/*!
1281
 * \brief Make logging more verbose
1282
 *
1283
 * If logging to stderr is not already enabled when this function is called,
1284
 * enable it. Otherwise, increase the log level by 1.
1285
 *
1286
 * \param[in] argc  Ignored
1287
 * \param[in] argv  Ignored
1288
 */
1289
void
1290
crm_bump_log_level(int argc, char **argv)
1291
0
{
1292
0
    if (qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0)
1293
0
        != QB_LOG_STATE_ENABLED) {
1294
0
        crm_enable_stderr(TRUE);
1295
0
    } else {
1296
0
        set_crm_log_level(crm_log_level + 1);
1297
0
    }
1298
0
}
1299
1300
unsigned int
1301
get_crm_log_level(void)
1302
0
{
1303
0
    return crm_log_level;
1304
0
}
1305
1306
/*!
1307
 * \brief Log the command line (once)
1308
 *
1309
 * \param[in]  Number of values in \p argv
1310
 * \param[in]  Command-line arguments (including command name)
1311
 *
1312
 * \note This function will only log once, even if called with different
1313
 *       arguments.
1314
 */
1315
void
1316
crm_log_args(int argc, char **argv)
1317
0
{
1318
0
    static bool logged = false;
1319
0
    gchar *arg_string = NULL;
1320
1321
0
    if ((argc == 0) || (argv == NULL) || logged) {
1322
0
        return;
1323
0
    }
1324
0
    logged = true;
1325
0
    arg_string = g_strjoinv(" ", argv);
1326
0
    pcmk__notice("Invoked: %s", arg_string);
1327
0
    g_free(arg_string);
1328
0
}
1329
1330
void
1331
crm_log_output_fn(const char *file, const char *function, int line, int level, const char *prefix,
1332
                  const char *output)
1333
0
{
1334
0
    gchar **out_lines = NULL;
1335
1336
0
    if (level == PCMK__LOG_NEVER) {
1337
0
        return;
1338
0
    }
1339
1340
0
    if (output == NULL) {
1341
0
        if (level != PCMK__LOG_STDOUT) {
1342
0
            level = LOG_TRACE;
1343
0
        }
1344
0
        output = "-- empty --";
1345
0
    }
1346
1347
0
    out_lines = g_strsplit(output, "\n", 0);
1348
1349
0
    for (gchar **out_line = out_lines; *out_line != NULL; out_line++) {
1350
0
        do_crm_log_alias(level, file, function, line, "%s [ %s ]",
1351
0
                         prefix, *out_line);
1352
0
    }
1353
1354
0
    g_strfreev(out_lines);
1355
0
}
1356
1357
void
1358
pcmk__cli_init_logging(const char *name, unsigned int verbosity)
1359
0
{
1360
0
    crm_log_init(name, LOG_ERR, FALSE, FALSE, 0, NULL, TRUE);
1361
1362
0
    for (int i = 0; i < verbosity; i++) {
1363
        /* These arguments are ignored, so pass placeholders. */
1364
0
        crm_bump_log_level(0, NULL);
1365
0
    }
1366
0
}
1367
1368
/*!
1369
 * \brief Log XML line-by-line in a formatted fashion
1370
 *
1371
 * \param[in] file      File name to use for log filtering
1372
 * \param[in] function  Function name to use for log filtering
1373
 * \param[in] line      Line number to use for log filtering
1374
 * \param[in] tags      Logging tags to use for log filtering
1375
 * \param[in] level     Priority at which to log the messages
1376
 * \param[in] text      Prefix for each line
1377
 * \param[in] xml       XML to log
1378
 *
1379
 * \note This does nothing when \p level is \c PCMK__LOG_STDOUT.
1380
 * \note Do not call this function directly. It should be called only from the
1381
 *       \p do_crm_log_xml() macro.
1382
 */
1383
void
1384
pcmk_log_xml_as(const char *file, const char *function, uint32_t line,
1385
                uint32_t tags, uint8_t level, const char *text, const xmlNode *xml)
1386
0
{
1387
0
    if (xml == NULL) {
1388
0
        do_crm_log(level, "%s%sNo data to dump as XML",
1389
0
                   pcmk__s(text, ""), pcmk__str_empty(text)? "" : " ");
1390
1391
0
    } else {
1392
0
        if (logger_out == NULL) {
1393
0
            CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return);
1394
0
        }
1395
1396
0
        pcmk__output_set_log_level(logger_out, level);
1397
0
        pcmk__output_set_log_filter(logger_out, file, function, line, tags);
1398
0
        pcmk__xml_show(logger_out, text, xml, 1,
1399
0
                       pcmk__xml_fmt_pretty
1400
0
                       |pcmk__xml_fmt_open
1401
0
                       |pcmk__xml_fmt_children
1402
0
                       |pcmk__xml_fmt_close);
1403
0
        pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U);
1404
0
    }
1405
0
}
1406
1407
/*!
1408
 * \internal
1409
 * \brief Log XML changes line-by-line in a formatted fashion
1410
 *
1411
 * \param[in] file      File name to use for log filtering
1412
 * \param[in] function  Function name to use for log filtering
1413
 * \param[in] line      Line number to use for log filtering
1414
 * \param[in] tags      Logging tags to use for log filtering
1415
 * \param[in] level     Priority at which to log the messages
1416
 * \param[in] xml       XML whose changes to log
1417
 *
1418
 * \note This does nothing when \p level is \c PCMK__LOG_STDOUT.
1419
 */
1420
void
1421
pcmk__log_xml_changes_as(const char *file, const char *function, uint32_t line,
1422
                         uint32_t tags, uint8_t level, const xmlNode *xml)
1423
0
{
1424
0
    if (xml == NULL) {
1425
0
        do_crm_log(level, "No XML to dump");
1426
0
        return;
1427
0
    }
1428
1429
0
    if (logger_out == NULL) {
1430
0
        CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return);
1431
0
    }
1432
0
    pcmk__output_set_log_level(logger_out, level);
1433
0
    pcmk__output_set_log_filter(logger_out, file, function, line, tags);
1434
0
    pcmk__xml_show_changes(logger_out, xml);
1435
0
    pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U);
1436
0
}
1437
1438
/*!
1439
 * \internal
1440
 * \brief Log an XML patchset line-by-line in a formatted fashion
1441
 *
1442
 * \param[in] file      File name to use for log filtering
1443
 * \param[in] function  Function name to use for log filtering
1444
 * \param[in] line      Line number to use for log filtering
1445
 * \param[in] tags      Logging tags to use for log filtering
1446
 * \param[in] level     Priority at which to log the messages
1447
 * \param[in] patchset  XML patchset to log
1448
 *
1449
 * \note This does nothing when \p level is \c PCMK__LOG_STDOUT.
1450
 */
1451
void
1452
pcmk__log_xml_patchset_as(const char *file, const char *function, uint32_t line,
1453
                          uint32_t tags, uint8_t level, const xmlNode *patchset)
1454
0
{
1455
0
    if (patchset == NULL) {
1456
0
        do_crm_log(level, "No patchset to dump");
1457
0
        return;
1458
0
    }
1459
1460
0
    if (logger_out == NULL) {
1461
0
        CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return);
1462
0
    }
1463
0
    pcmk__output_set_log_level(logger_out, level);
1464
0
    pcmk__output_set_log_filter(logger_out, file, function, line, tags);
1465
0
    logger_out->message(logger_out, "xml-patchset", patchset);
1466
0
    pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U);
1467
0
}
1468
1469
void pcmk__set_config_error_handler(pcmk__config_error_func error_handler, void *error_context)
1470
0
{
1471
0
    pcmk__config_error_handler = error_handler;
1472
0
    pcmk__config_error_context = error_context;    
1473
0
}
1474
1475
void pcmk__set_config_warning_handler(pcmk__config_warning_func warning_handler, void *warning_context)
1476
0
{
1477
0
    pcmk__config_warning_handler = warning_handler;
1478
0
    pcmk__config_warning_context = warning_context;   
1479
0
}