root/lib/common/output_log.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. log_subprocess_output
  2. log_free_priv
  3. log_init
  4. log_finish
  5. log_reset
  6. log_version
  7. G_GNUC_PRINTF
  8. log_output_xml
  9. G_GNUC_PRINTF
  10. G_GNUC_PRINTF
  11. log_end_list
  12. G_GNUC_PRINTF
  13. G_GNUC_PRINTF
  14. log_is_quiet
  15. log_spacer
  16. log_progress
  17. log_prompt
  18. pcmk__mk_log_output
  19. pcmk__output_get_log_level
  20. pcmk__output_set_log_level
  21. pcmk__output_set_log_filter

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

/* [previous][next][first][last][top][bottom][index][help] */