Line data Source code
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 0 : 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 0 : }
65 :
66 : static void
67 0 : log_free_priv(pcmk__output_t *out) {
68 0 : private_data_t *priv = NULL;
69 :
70 0 : if (out == NULL || out->priv == NULL) {
71 0 : return;
72 : }
73 :
74 0 : priv = out->priv;
75 :
76 0 : g_queue_free(priv->prefixes);
77 0 : free(priv);
78 0 : out->priv = NULL;
79 : }
80 :
81 : static bool
82 0 : log_init(pcmk__output_t *out) {
83 0 : private_data_t *priv = NULL;
84 :
85 0 : CRM_ASSERT(out != NULL);
86 :
87 : /* If log_init was previously called on this output struct, just return. */
88 0 : if (out->priv != NULL) {
89 0 : return true;
90 : }
91 :
92 0 : out->priv = calloc(1, sizeof(private_data_t));
93 0 : if (out->priv == NULL) {
94 0 : return false;
95 : }
96 :
97 0 : priv = out->priv;
98 :
99 0 : priv->prefixes = g_queue_new();
100 0 : priv->log_level = LOG_INFO;
101 :
102 0 : return true;
103 : }
104 :
105 : static void
106 0 : log_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
107 : /* This function intentionally left blank */
108 0 : }
109 :
110 : static void
111 0 : log_reset(pcmk__output_t *out) {
112 0 : CRM_ASSERT(out != NULL);
113 :
114 0 : out->dest = freopen(NULL, "w", out->dest);
115 0 : CRM_ASSERT(out->dest != NULL);
116 :
117 0 : log_free_priv(out);
118 0 : log_init(out);
119 0 : }
120 :
121 : static void
122 0 : log_version(pcmk__output_t *out, bool extended) {
123 0 : private_data_t *priv = NULL;
124 :
125 0 : CRM_ASSERT(out != NULL && out->priv != NULL);
126 0 : priv = out->priv;
127 :
128 0 : if (extended) {
129 0 : logger(priv, "Pacemaker %s (Build: %s): %s",
130 : PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
131 : } else {
132 0 : logger(priv, "Pacemaker " PACEMAKER_VERSION);
133 0 : logger(priv, "Written by Andrew Beekhof and "
134 : "the Pacemaker project contributors");
135 : }
136 0 : }
137 :
138 : G_GNUC_PRINTF(2, 3)
139 : static void
140 0 : log_err(pcmk__output_t *out, const char *format, ...)
141 : {
142 : va_list ap;
143 0 : private_data_t *priv = NULL;
144 :
145 0 : CRM_ASSERT((out != NULL) && (out->priv != NULL));
146 0 : priv = out->priv;
147 :
148 : /* Error output does not get indented, to separate it from other
149 : * potentially indented list output.
150 : */
151 0 : va_start(ap, format);
152 0 : logger_va(priv, LOG_ERR, format, ap);
153 0 : va_end(ap);
154 0 : }
155 :
156 : static void
157 0 : log_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
158 0 : xmlNodePtr node = NULL;
159 0 : private_data_t *priv = NULL;
160 :
161 0 : CRM_ASSERT(out != NULL && out->priv != NULL);
162 0 : priv = out->priv;
163 :
164 0 : node = pcmk__xe_create(NULL, name);
165 0 : pcmk__xe_set_content(node, "%s", buf);
166 0 : do_crm_log_xml(priv->log_level, name, node);
167 0 : free(node);
168 0 : }
169 :
170 : G_GNUC_PRINTF(4, 5)
171 : static void
172 0 : log_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun,
173 : const char *format, ...) {
174 0 : int len = 0;
175 : va_list ap;
176 0 : char* buffer = NULL;
177 0 : private_data_t *priv = NULL;
178 :
179 0 : CRM_ASSERT(out != NULL && out->priv != NULL);
180 0 : priv = out->priv;
181 :
182 0 : va_start(ap, format);
183 0 : len = vasprintf(&buffer, format, ap);
184 0 : CRM_ASSERT(len >= 0);
185 0 : va_end(ap);
186 :
187 : /* Don't skip empty prefixes,
188 : * otherwise there will be mismatch
189 : * in the log_end_list */
190 0 : if(strcmp(buffer, "") == 0) {
191 : /* nothing */
192 : }
193 :
194 0 : g_queue_push_tail(priv->prefixes, buffer);
195 0 : }
196 :
197 : G_GNUC_PRINTF(3, 4)
198 : static void
199 0 : log_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
200 0 : int len = 0;
201 : va_list ap;
202 0 : private_data_t *priv = NULL;
203 0 : char prefix[LINE_MAX] = { 0 };
204 0 : int offset = 0;
205 0 : char* buffer = NULL;
206 :
207 0 : CRM_ASSERT(out != NULL && out->priv != NULL);
208 0 : priv = out->priv;
209 :
210 0 : for (GList* gIter = priv->prefixes->head; gIter; gIter = gIter->next) {
211 0 : if (strcmp(prefix, "") != 0) {
212 0 : offset += snprintf(prefix + offset, LINE_MAX - offset, ": %s", (char *)gIter->data);
213 : } else {
214 0 : offset = snprintf(prefix, LINE_MAX, "%s", (char *)gIter->data);
215 : }
216 : }
217 :
218 0 : va_start(ap, format);
219 0 : len = vasprintf(&buffer, format, ap);
220 0 : CRM_ASSERT(len >= 0);
221 0 : va_end(ap);
222 :
223 0 : if (strcmp(buffer, "") != 0) { /* We don't want empty messages */
224 0 : if ((name != NULL) && (strcmp(name, "") != 0)) {
225 0 : if (strcmp(prefix, "") != 0) {
226 0 : logger(priv, "%s: %s: %s", prefix, name, buffer);
227 : } else {
228 0 : logger(priv, "%s: %s", name, buffer);
229 : }
230 : } else {
231 0 : if (strcmp(prefix, "") != 0) {
232 0 : logger(priv, "%s: %s", prefix, buffer);
233 : } else {
234 0 : logger(priv, "%s", buffer);
235 : }
236 : }
237 : }
238 0 : free(buffer);
239 0 : }
240 :
241 : static void
242 0 : log_end_list(pcmk__output_t *out) {
243 0 : private_data_t *priv = NULL;
244 :
245 0 : CRM_ASSERT(out != NULL && out->priv != NULL);
246 0 : priv = out->priv;
247 :
248 0 : if (priv->prefixes == NULL) {
249 0 : return;
250 : }
251 0 : CRM_ASSERT(priv->prefixes->tail != NULL);
252 :
253 0 : free((char *)priv->prefixes->tail->data);
254 0 : g_queue_pop_tail(priv->prefixes);
255 : }
256 :
257 : G_GNUC_PRINTF(2, 3)
258 : static int
259 0 : log_info(pcmk__output_t *out, const char *format, ...)
260 : {
261 : va_list ap;
262 0 : private_data_t *priv = NULL;
263 :
264 0 : CRM_ASSERT(out != NULL && out->priv != NULL);
265 0 : priv = out->priv;
266 :
267 : /* Informational output does not get indented, to separate it from other
268 : * potentially indented list output.
269 : */
270 0 : va_start(ap, format);
271 0 : logger_va(priv, priv->log_level, format, ap);
272 0 : va_end(ap);
273 :
274 0 : return pcmk_rc_ok;
275 : }
276 :
277 : G_GNUC_PRINTF(2, 3)
278 : static int
279 0 : log_transient(pcmk__output_t *out, const char *format, ...)
280 : {
281 : va_list ap;
282 0 : private_data_t *priv = NULL;
283 :
284 0 : CRM_ASSERT(out != NULL && out->priv != NULL);
285 0 : priv = out->priv;
286 :
287 0 : va_start(ap, format);
288 0 : logger_va(priv, QB_MAX(priv->log_level, LOG_DEBUG), format, ap);
289 0 : va_end(ap);
290 :
291 0 : return pcmk_rc_ok;
292 : }
293 :
294 : static bool
295 0 : log_is_quiet(pcmk__output_t *out) {
296 0 : return false;
297 : }
298 :
299 : static void
300 0 : log_spacer(pcmk__output_t *out) {
301 : /* This function intentionally left blank */
302 0 : }
303 :
304 : static void
305 0 : log_progress(pcmk__output_t *out, bool end) {
306 : /* This function intentionally left blank */
307 0 : }
308 :
309 : static void
310 0 : log_prompt(const char *prompt, bool echo, char **dest) {
311 : /* This function intentionally left blank */
312 0 : }
313 :
314 : pcmk__output_t *
315 0 : pcmk__mk_log_output(char **argv) {
316 0 : pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
317 :
318 0 : if (retval == NULL) {
319 0 : return NULL;
320 : }
321 :
322 0 : retval->fmt_name = "log";
323 0 : retval->request = pcmk__quote_cmdline(argv);
324 :
325 0 : retval->init = log_init;
326 0 : retval->free_priv = log_free_priv;
327 0 : retval->finish = log_finish;
328 0 : retval->reset = log_reset;
329 :
330 0 : retval->register_message = pcmk__register_message;
331 0 : retval->message = pcmk__call_message;
332 :
333 0 : retval->subprocess_output = log_subprocess_output;
334 0 : retval->version = log_version;
335 0 : retval->info = log_info;
336 0 : retval->transient = log_transient;
337 0 : retval->err = log_err;
338 0 : retval->output_xml = log_output_xml;
339 :
340 0 : retval->begin_list = log_begin_list;
341 0 : retval->list_item = log_list_item;
342 0 : retval->end_list = log_end_list;
343 :
344 0 : retval->is_quiet = log_is_quiet;
345 0 : retval->spacer = log_spacer;
346 0 : retval->progress = log_progress;
347 0 : retval->prompt = log_prompt;
348 :
349 0 : 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 0 : pcmk__output_get_log_level(const pcmk__output_t *out)
364 : {
365 0 : CRM_ASSERT(out != NULL);
366 :
367 0 : if (pcmk__str_eq(out->fmt_name, "log", pcmk__str_none)) {
368 0 : private_data_t *priv = out->priv;
369 :
370 0 : CRM_ASSERT(priv != NULL);
371 0 : return priv->log_level;
372 : }
373 0 : 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 0 : pcmk__output_set_log_level(pcmk__output_t *out, uint8_t log_level)
391 : {
392 0 : CRM_ASSERT(out != NULL);
393 :
394 0 : if (pcmk__str_eq(out->fmt_name, "log", pcmk__str_none)) {
395 0 : private_data_t *priv = out->priv;
396 :
397 0 : CRM_ASSERT(priv != NULL);
398 0 : priv->log_level = log_level;
399 : }
400 0 : }
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 0 : pcmk__output_set_log_filter(pcmk__output_t *out, const char *file,
420 : const char *function, uint32_t line, uint32_t tags)
421 : {
422 0 : CRM_ASSERT(out != NULL);
423 :
424 0 : if (pcmk__str_eq(out->fmt_name, "log", pcmk__str_none)) {
425 0 : private_data_t *priv = out->priv;
426 :
427 0 : CRM_ASSERT(priv != NULL);
428 0 : priv->file = file;
429 0 : priv->function = function;
430 0 : priv->line = line;
431 0 : priv->tags = tags;
432 : }
433 0 : }
|