1 /*
2 Copyright 2020 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <logging.h>
26 #include <alloc.h>
27 #include <string_lib.h>
28 #include <misc_lib.h>
29 #include <cleanup.h>
30
31 #ifdef __MINGW32__
32 #include <definitions.h> /* CF_BUFSIZE */
33 #endif
34
35 char VPREFIX[1024] = ""; /* GLOBAL_C */
36
37 static char AgentType[80] = "generic";
38 static bool TIMESTAMPS = false;
39
40 static LogLevel global_level = LOG_LEVEL_NOTICE; /* GLOBAL_X */
41
42 static pthread_once_t log_context_init_once = PTHREAD_ONCE_INIT; /* GLOBAL_T */
43 static pthread_key_t log_context_key; /* GLOBAL_T, initialized by pthread_key_create */
44
LoggingInitializeOnce(void)45 static void LoggingInitializeOnce(void)
46 {
47 if (pthread_key_create(&log_context_key, &free) != 0)
48 {
49 /* There is no way to signal error out of pthread_once callback.
50 * However if pthread_key_create fails we are pretty much guaranteed
51 * that nothing else will work. */
52
53 fprintf(stderr, "Unable to initialize logging subsystem\n");
54 DoCleanupAndExit(255);
55 }
56 }
57
GetCurrentThreadContext(void)58 LoggingContext *GetCurrentThreadContext(void)
59 {
60 pthread_once(&log_context_init_once, &LoggingInitializeOnce);
61 LoggingContext *lctx = pthread_getspecific(log_context_key);
62 if (lctx == NULL)
63 {
64 lctx = xcalloc(1, sizeof(LoggingContext));
65 lctx->log_level = global_level;
66 lctx->report_level = global_level;
67 pthread_setspecific(log_context_key, lctx);
68 }
69 return lctx;
70 }
71
LoggingFreeCurrentThreadContext(void)72 void LoggingFreeCurrentThreadContext(void)
73 {
74 pthread_once(&log_context_init_once, &LoggingInitializeOnce);
75 LoggingContext *lctx = pthread_getspecific(log_context_key);
76 if (lctx == NULL)
77 {
78 return;
79 }
80 // lctx->pctx is usually stack allocated and shouldn't be freed
81 FREE_AND_NULL(lctx);
82 pthread_setspecific(log_context_key, NULL);
83 }
84
LoggingSetAgentType(const char * type)85 void LoggingSetAgentType(const char *type)
86 {
87 strlcpy(AgentType, type, sizeof(AgentType));
88 }
89
LoggingEnableTimestamps(bool enable)90 void LoggingEnableTimestamps(bool enable)
91 {
92 TIMESTAMPS = enable;
93 }
94
LoggingPrivSetContext(LoggingPrivContext * pctx)95 void LoggingPrivSetContext(LoggingPrivContext *pctx)
96 {
97 LoggingContext *lctx = GetCurrentThreadContext();
98 lctx->pctx = pctx;
99 }
100
LoggingPrivGetContext(void)101 LoggingPrivContext *LoggingPrivGetContext(void)
102 {
103 LoggingContext *lctx = GetCurrentThreadContext();
104 return lctx->pctx;
105 }
106
LoggingPrivSetLevels(LogLevel log_level,LogLevel report_level)107 void LoggingPrivSetLevels(LogLevel log_level, LogLevel report_level)
108 {
109 LoggingContext *lctx = GetCurrentThreadContext();
110 lctx->log_level = log_level;
111 lctx->report_level = report_level;
112 }
113
LogLevelToString(LogLevel level)114 const char *LogLevelToString(LogLevel level)
115 {
116 switch (level)
117 {
118 case LOG_LEVEL_CRIT:
119 return "CRITICAL";
120 case LOG_LEVEL_ERR:
121 return "error";
122 case LOG_LEVEL_WARNING:
123 return "warning";
124 case LOG_LEVEL_NOTICE:
125 return "notice";
126 case LOG_LEVEL_INFO:
127 return "info";
128 case LOG_LEVEL_VERBOSE:
129 return "verbose";
130 case LOG_LEVEL_DEBUG:
131 return "debug";
132 default:
133 ProgrammingError("LogLevelToString: Unexpected log level %d", level);
134 }
135 }
136
LogLevelFromString(const char * const level)137 LogLevel LogLevelFromString(const char *const level)
138 {
139 // Only compare the part the user typed
140 // i/info/inform/information will all result in LOG_LEVEL_INFO
141 size_t len = SafeStringLength(level);
142 if (len == 0)
143 {
144 return LOG_LEVEL_NOTHING;
145 }
146 if (StringEqualN_IgnoreCase(level, "CRITICAL", len))
147 {
148 return LOG_LEVEL_CRIT;
149 }
150 if (StringEqualN_IgnoreCase(level, "errors", len))
151 {
152 return LOG_LEVEL_ERR;
153 }
154 if (StringEqualN_IgnoreCase(level, "warnings", len))
155 {
156 return LOG_LEVEL_WARNING;
157 }
158 if (StringEqualN_IgnoreCase(level, "notices", len))
159 {
160 return LOG_LEVEL_NOTICE;
161 }
162 if (StringEqualN_IgnoreCase(level, "information", len))
163 {
164 return LOG_LEVEL_INFO;
165 }
166 if (StringEqualN_IgnoreCase(level, "verbose", len))
167 {
168 return LOG_LEVEL_VERBOSE;
169 }
170 if (StringEqualN_IgnoreCase(level, "debug", len))
171 {
172 return LOG_LEVEL_DEBUG;
173 }
174 return LOG_LEVEL_NOTHING;
175 }
176
LogLevelToColor(LogLevel level)177 static const char *LogLevelToColor(LogLevel level)
178 {
179
180 switch (level)
181 {
182 case LOG_LEVEL_CRIT:
183 case LOG_LEVEL_ERR:
184 return "\x1b[31m"; // red
185
186 case LOG_LEVEL_WARNING:
187 return "\x1b[33m"; // yellow
188
189 case LOG_LEVEL_NOTICE:
190 case LOG_LEVEL_INFO:
191 return "\x1b[32m"; // green
192
193 case LOG_LEVEL_VERBOSE:
194 case LOG_LEVEL_DEBUG:
195 return "\x1b[34m"; // blue
196
197 default:
198 ProgrammingError("LogLevelToColor: Unexpected log level %d", level);
199 }
200 }
201
LoggingFormatTimestamp(char dest[64],size_t n,struct tm * timestamp)202 bool LoggingFormatTimestamp(char dest[64], size_t n, struct tm *timestamp)
203 {
204 if (strftime(dest, n, "%Y-%m-%dT%H:%M:%S%z", timestamp) == 0)
205 {
206 strlcpy(dest, "<unknown>", n);
207 return false;
208 }
209 return true;
210 }
211
LogToConsole(const char * msg,LogLevel level,bool color)212 static void LogToConsole(const char *msg, LogLevel level, bool color)
213 {
214 struct tm now;
215 time_t now_seconds = time(NULL);
216 localtime_r(&now_seconds, &now);
217
218 if (color)
219 {
220 fprintf(stdout, "%s", LogLevelToColor(level));
221 }
222 if (level >= LOG_LEVEL_INFO && VPREFIX[0])
223 {
224 fprintf(stdout, "%s ", VPREFIX);
225 }
226 if (TIMESTAMPS)
227 {
228 char formatted_timestamp[64];
229 LoggingFormatTimestamp(formatted_timestamp, 64, &now);
230 fprintf(stdout, "%s ", formatted_timestamp);
231 }
232
233 fprintf(stdout, "%8s: %s\n", LogLevelToString(level), msg);
234
235 if (color)
236 {
237 // Turn off the color again.
238 fprintf(stdout, "\x1b[0m");
239 }
240
241 fflush(stdout);
242 }
243
244 #if !defined(__MINGW32__)
LogLevelToSyslogPriority(LogLevel level)245 static int LogLevelToSyslogPriority(LogLevel level)
246 {
247 switch (level)
248 {
249 case LOG_LEVEL_CRIT: return LOG_CRIT;
250 case LOG_LEVEL_ERR: return LOG_ERR;
251 case LOG_LEVEL_WARNING: return LOG_WARNING;
252 case LOG_LEVEL_NOTICE: return LOG_NOTICE;
253 case LOG_LEVEL_INFO: return LOG_INFO;
254 case LOG_LEVEL_VERBOSE: return LOG_DEBUG; /* FIXME: Do we really want to conflate those levels? */
255 case LOG_LEVEL_DEBUG: return LOG_DEBUG;
256 default:
257 ProgrammingError("LogLevelToSyslogPriority: Unexpected log level %d",
258 level);
259 }
260
261 }
262
LogToSystemLog(const char * msg,LogLevel level)263 void LogToSystemLog(const char *msg, LogLevel level)
264 {
265 char logmsg[4096];
266 snprintf(logmsg, sizeof(logmsg), "CFEngine(%s) %s %s\n", AgentType, VPREFIX, msg);
267 syslog(LogLevelToSyslogPriority(level), "%s", logmsg);
268 }
269 #endif /* !__MINGW32__ */
270
271 #ifndef __MINGW32__
GetErrorStrFromCode(int error_code)272 const char *GetErrorStrFromCode(int error_code)
273 {
274 return strerror(error_code);
275 }
276
GetErrorStr(void)277 const char *GetErrorStr(void)
278 {
279 return strerror(errno);
280 }
281
282 #else
283
GetErrorStrFromCode(int error_code)284 const char *GetErrorStrFromCode(int error_code)
285 {
286 static char errbuf[CF_BUFSIZE];
287 int len;
288
289 memset(errbuf, 0, sizeof(errbuf));
290
291 if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, error_code,
292 MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), errbuf, CF_BUFSIZE, NULL))
293 {
294 // remove CRLF from end
295 len = strlen(errbuf);
296 errbuf[len - 2] = errbuf[len - 1] = '\0';
297 } else {
298 strcpy(errbuf, "Unknown error");
299 }
300
301 return errbuf;
302 }
303
GetErrorStr(void)304 const char *GetErrorStr(void)
305 {
306 return GetErrorStrFromCode(GetLastError());
307 }
308 #endif /* !__MINGW32__ */
309
WouldLog(LogLevel level)310 bool WouldLog(LogLevel level)
311 {
312 LoggingContext *lctx = GetCurrentThreadContext();
313
314 bool log_to_console = (level <= lctx->report_level);
315 bool log_to_syslog = (level <= lctx->log_level &&
316 level < LOG_LEVEL_VERBOSE);
317 bool force_hook = (lctx->pctx &&
318 lctx->pctx->log_hook &&
319 lctx->pctx->force_hook_level >= level);
320
321 return (log_to_console || log_to_syslog || force_hook);
322 }
323
VLog(LogLevel level,const char * fmt,va_list ap)324 void VLog(LogLevel level, const char *fmt, va_list ap)
325 {
326 LoggingContext *lctx = GetCurrentThreadContext();
327
328 bool log_to_console = ( level <= lctx->report_level );
329 bool log_to_syslog = ( level <= lctx->log_level &&
330 level < LOG_LEVEL_VERBOSE );
331 bool force_hook = ( lctx->pctx &&
332 lctx->pctx->log_hook &&
333 lctx->pctx->force_hook_level >= level );
334
335 /* NEEDS TO BE IN SYNC WITH THE CONDITION IN WouldLog() ABOVE! */
336 if (!log_to_console && !log_to_syslog && !force_hook)
337 {
338 return; /* early return - save resources */
339 }
340
341 char *msg = StringVFormat(fmt, ap);
342 char *hooked_msg = NULL;
343
344 /* Remove ending EOLN. */
345 for (char *sp = msg; *sp != '\0'; sp++)
346 {
347 if (*sp == '\n' && *(sp+1) == '\0')
348 {
349 *sp = '\0';
350 break;
351 }
352 }
353
354 if (lctx->pctx && lctx->pctx->log_hook)
355 {
356 hooked_msg = lctx->pctx->log_hook(lctx->pctx, level, msg);
357 }
358 else
359 {
360 hooked_msg = msg;
361 }
362
363 if (log_to_console)
364 {
365 LogToConsole(hooked_msg, level, lctx->color);
366 }
367 if (log_to_syslog)
368 {
369 LogToSystemLog(hooked_msg, level);
370 }
371
372 if (hooked_msg != msg)
373 {
374 free(hooked_msg);
375 }
376 free(msg);
377 }
378
379 /* TODO create libutils/defs.h. */
380 #define CF_MAX_BUFSIZE 16384
381
382 /**
383 * @brief Logs binary data in #buf, with unprintable bytes translated to '.'.
384 * Message is prefixed with #prefix.
385 * @param #buflen must be no more than CF_MAX_BUFSIZE
386 */
LogRaw(LogLevel level,const char * prefix,const void * buf,size_t buflen)387 void LogRaw(LogLevel level, const char *prefix, const void *buf, size_t buflen)
388 {
389 const unsigned char *src = buf;
390 unsigned char dst[buflen+1];
391 assert(sizeof(dst) <= CF_MAX_BUFSIZE);
392
393 LoggingContext *lctx = GetCurrentThreadContext();
394 if (level <= lctx->report_level || level <= lctx->log_level)
395 {
396 size_t i;
397 for (i = 0; i < buflen; i++)
398 {
399 dst[i] = isprint(src[i]) ? src[i] : '.';
400 }
401 dst[i] = '\0';
402
403 /* And Log the translated buffer, which is now a valid string. */
404 Log(level, "%s%s", prefix, dst);
405 }
406 }
407
Log(LogLevel level,const char * fmt,...)408 void Log(LogLevel level, const char *fmt, ...)
409 {
410 va_list ap;
411 va_start(ap, fmt);
412 VLog(level, fmt, ap);
413 va_end(ap);
414 }
415
416
417
418 static bool module_is_enabled[LOG_MOD_MAX];
419 static const char *log_modules[LOG_MOD_MAX] =
420 {
421 "",
422 "evalctx",
423 "expand",
424 "iterations",
425 "parser",
426 "vartable",
427 "vars",
428 "locks",
429 "ps",
430 };
431
LogModuleFromString(const char * s)432 static enum LogModule LogModuleFromString(const char *s)
433 {
434 for (enum LogModule i = 0; i < LOG_MOD_MAX; i++)
435 {
436 if (strcmp(log_modules[i], s) == 0)
437 {
438 return i;
439 }
440 }
441
442 return LOG_MOD_NONE;
443 }
444
LogEnableModule(enum LogModule mod)445 void LogEnableModule(enum LogModule mod)
446 {
447 assert(mod < LOG_MOD_MAX);
448
449 module_is_enabled[mod] = true;
450 }
451
LogModuleHelp(void)452 void LogModuleHelp(void)
453 {
454 printf("\n--log-modules accepts a comma separated list of one or more of the following:\n\n");
455 printf(" help\n");
456 printf(" all\n");
457 for (enum LogModule i = LOG_MOD_NONE + 1; i < LOG_MOD_MAX; i++)
458 {
459 printf(" %s\n", log_modules[i]);
460 }
461 printf("\n");
462 }
463
464 /**
465 * Parse a string of modules, and enable the relevant DEBUG logging modules.
466 * Example strings:
467 *
468 * all : enables all debug modules
469 * help : enables nothing, but prints a help message
470 * iterctx : enables the "iterctx" debug logging module
471 * iterctx,vars: enables the 2 debug modules, "iterctx" and "vars"
472 *
473 * @NOTE modifies string #s but restores it before returning.
474 */
LogEnableModulesFromString(char * s)475 bool LogEnableModulesFromString(char *s)
476 {
477 bool retval = true;
478
479 const char *token = s;
480 char saved_sep = ','; /* any non-NULL value will do */
481 while (saved_sep != '\0' && retval != false)
482 {
483 char *next_token = strchrnul(token, ',');
484 saved_sep = *next_token;
485 *next_token = '\0'; /* modify parameter s */
486 size_t token_len = next_token - token;
487
488 if (strcmp(token, "help") == 0)
489 {
490 LogModuleHelp();
491 retval = false; /* early exit */
492 }
493 else if (strcmp(token, "all") == 0)
494 {
495 for (enum LogModule j = LOG_MOD_NONE + 1; j < LOG_MOD_MAX; j++)
496 {
497 LogEnableModule(j);
498 }
499 }
500 else
501 {
502 enum LogModule mod = LogModuleFromString(token);
503
504 assert(mod < LOG_MOD_MAX);
505 if (mod == LOG_MOD_NONE)
506 {
507 Log(LOG_LEVEL_WARNING,
508 "Unknown debug logging module '%*s'",
509 (int) token_len, token);
510 }
511 else
512 {
513 LogEnableModule(mod);
514 }
515 }
516
517
518 *next_token = saved_sep; /* restore modified parameter s */
519 next_token++; /* bypass comma */
520 token = next_token;
521 }
522
523 return retval;
524 }
525
LogModuleEnabled(enum LogModule mod)526 bool LogModuleEnabled(enum LogModule mod)
527 {
528 assert(mod > LOG_MOD_NONE);
529 assert(mod < LOG_MOD_MAX);
530
531 if (module_is_enabled[mod])
532 {
533 return true;
534 }
535 else
536 {
537 return false;
538 }
539 }
540
LogDebug(enum LogModule mod,const char * fmt,...)541 void LogDebug(enum LogModule mod, const char *fmt, ...)
542 {
543 assert(mod < LOG_MOD_MAX);
544
545 /* Did we forget any entry in log_modules? Should be a static assert. */
546 assert(sizeof(log_modules) / sizeof(log_modules[0]) == LOG_MOD_MAX);
547
548 if (LogModuleEnabled(mod))
549 {
550 va_list ap;
551 va_start(ap, fmt);
552 VLog(LOG_LEVEL_DEBUG, fmt, ap);
553 va_end(ap);
554 /* VLog(LOG_LEVEL_DEBUG, "%s: ...", */
555 /* debug_modules_description[mod_order], ...); */
556 }
557 }
558
559
LogSetGlobalLevel(LogLevel level)560 void LogSetGlobalLevel(LogLevel level)
561 {
562 global_level = level;
563 LoggingPrivSetLevels(level, level);
564 }
565
LogSetGlobalLevelArgOrExit(const char * const arg)566 void LogSetGlobalLevelArgOrExit(const char *const arg)
567 {
568 LogLevel level = LogLevelFromString(arg);
569 if (level == LOG_LEVEL_NOTHING)
570 {
571 // This function is used as part of initializing the logging
572 // system. Using Log() can be considered incorrect, even though
573 // it may "work". Let's just print an error to stderr:
574 fprintf(stderr, "Invalid log level: '%s'\n", arg);
575 DoCleanupAndExit(1);
576 }
577 LogSetGlobalLevel(level);
578 }
579
LogGetGlobalLevel(void)580 LogLevel LogGetGlobalLevel(void)
581 {
582 return global_level;
583 }
584
LoggingSetColor(bool enabled)585 void LoggingSetColor(bool enabled)
586 {
587 LoggingContext *lctx = GetCurrentThreadContext();
588 lctx->color = enabled;
589 }
590
591 // byte_magnitude and byte_unit are used to print human readable byte counts
byte_magnitude(long bytes)592 long byte_magnitude(long bytes)
593 {
594 const long Ki = 1024;
595 const long Mi = Ki * 1024;
596 const long Gi = Mi * 1024;
597
598 if (bytes > 8 * Gi)
599 {
600 return bytes / Gi;
601 }
602 else if (bytes > 8 * Mi)
603 {
604 return bytes / Mi;
605 }
606 else if (bytes > 8 * Ki)
607 {
608 return bytes / Ki;
609 }
610 return bytes;
611 }
612
613 // Use this with byte_magnitude
614 // Note that the cutoff is at 8x unit, because 3192 bytes is arguably more
615 // useful than 3KiB
byte_unit(long bytes)616 const char *byte_unit(long bytes)
617 {
618 const long Ki = 1024;
619 const long Mi = Ki * 1024;
620 const long Gi = Mi * 1024;
621
622 if (bytes > 8 * Gi)
623 {
624 return "GiB";
625 }
626 else if (bytes > 8 * Mi)
627 {
628 return "MiB";
629 }
630 else if (bytes > 8 * Ki)
631 {
632 return "KiB";
633 }
634 return "bytes";
635 }
636