Coverage Report

Created: 2023-06-07 07:02

/src/core/libntech/libutils/logging.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
  Copyright 2023 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
#include <sequence.h>
31
32
#include <definitions.h>        /* CF_BUFSIZE */
33
34
char VPREFIX[1024] = ""; /* GLOBAL_C */
35
36
static char AgentType[80] = "generic";
37
static bool TIMESTAMPS = false;
38
39
static LogLevel global_level = LOG_LEVEL_NOTICE; /* GLOBAL_X */
40
static LogLevel global_system_log_level = LOG_LEVEL_NOTHING; /* default value that means not set */
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
45
static Seq *log_buffer = NULL;
46
static bool logging_into_buffer = false;
47
static LogLevel min_log_into_buffer_level = LOG_LEVEL_NOTHING;
48
static LogLevel max_log_into_buffer_level = LOG_LEVEL_NOTHING;
49
50
typedef struct LogEntry_
51
{
52
    LogLevel level;
53
    char *msg;
54
} LogEntry;
55
56
static void LoggingInitializeOnce(void)
57
0
{
58
0
    if (pthread_key_create(&log_context_key, &free) != 0)
59
0
    {
60
        /* There is no way to signal error out of pthread_once callback.
61
         * However if pthread_key_create fails we are pretty much guaranteed
62
         * that nothing else will work. */
63
64
0
        fprintf(stderr, "Unable to initialize logging subsystem\n");
65
0
        DoCleanupAndExit(255);
66
0
    }
67
0
}
68
69
LoggingContext *GetCurrentThreadContext(void)
70
0
{
71
0
    pthread_once(&log_context_init_once, &LoggingInitializeOnce);
72
0
    LoggingContext *lctx = pthread_getspecific(log_context_key);
73
0
    if (lctx == NULL)
74
0
    {
75
0
        lctx = xcalloc(1, sizeof(LoggingContext));
76
0
        lctx->log_level = (global_system_log_level != LOG_LEVEL_NOTHING ?
77
0
                           global_system_log_level :
78
0
                           global_level);
79
0
        lctx->report_level = global_level;
80
0
        pthread_setspecific(log_context_key, lctx);
81
0
    }
82
0
    return lctx;
83
0
}
84
85
void LoggingFreeCurrentThreadContext(void)
86
0
{
87
0
    pthread_once(&log_context_init_once, &LoggingInitializeOnce);
88
0
    LoggingContext *lctx = pthread_getspecific(log_context_key);
89
0
    if (lctx == NULL)
90
0
    {
91
0
        return;
92
0
    }
93
    // lctx->pctx is usually stack allocated and shouldn't be freed
94
0
    FREE_AND_NULL(lctx);
95
0
    pthread_setspecific(log_context_key, NULL);
96
0
}
97
98
void LoggingSetAgentType(const char *type)
99
0
{
100
0
    strlcpy(AgentType, type, sizeof(AgentType));
101
0
}
102
103
void LoggingEnableTimestamps(bool enable)
104
0
{
105
0
    TIMESTAMPS = enable;
106
0
}
107
108
void LoggingPrivSetContext(LoggingPrivContext *pctx)
109
0
{
110
0
    LoggingContext *lctx = GetCurrentThreadContext();
111
0
    lctx->pctx = pctx;
112
0
}
113
114
LoggingPrivContext *LoggingPrivGetContext(void)
115
0
{
116
0
    LoggingContext *lctx = GetCurrentThreadContext();
117
0
    return lctx->pctx;
118
0
}
119
120
void LoggingPrivSetLevels(LogLevel log_level, LogLevel report_level)
121
0
{
122
0
    LoggingContext *lctx = GetCurrentThreadContext();
123
0
    lctx->log_level = log_level;
124
0
    lctx->report_level = report_level;
125
0
}
126
127
const char *LogLevelToString(LogLevel level)
128
0
{
129
0
    switch (level)
130
0
    {
131
0
    case LOG_LEVEL_CRIT:
132
0
        return "CRITICAL";
133
0
    case LOG_LEVEL_ERR:
134
0
        return "error";
135
0
    case LOG_LEVEL_WARNING:
136
0
        return "warning";
137
0
    case LOG_LEVEL_NOTICE:
138
0
        return "notice";
139
0
    case LOG_LEVEL_INFO:
140
0
        return "info";
141
0
    case LOG_LEVEL_VERBOSE:
142
0
        return "verbose";
143
0
    case LOG_LEVEL_DEBUG:
144
0
        return "debug";
145
0
    default:
146
0
        ProgrammingError("LogLevelToString: Unexpected log level %d", level);
147
0
    }
148
0
}
149
150
LogLevel LogLevelFromString(const char *const level)
151
0
{
152
    // Only compare the part the user typed
153
    // i/info/inform/information will all result in LOG_LEVEL_INFO
154
0
    size_t len = SafeStringLength(level);
155
0
    if (len == 0)
156
0
    {
157
0
        return LOG_LEVEL_NOTHING;
158
0
    }
159
0
    if (StringEqualN_IgnoreCase(level, "CRITICAL", len))
160
0
    {
161
0
        return LOG_LEVEL_CRIT;
162
0
    }
163
0
    if (StringEqualN_IgnoreCase(level, "errors", len))
164
0
    {
165
0
        return LOG_LEVEL_ERR;
166
0
    }
167
0
    if (StringEqualN_IgnoreCase(level, "warnings", len))
168
0
    {
169
0
        return LOG_LEVEL_WARNING;
170
0
    }
171
0
    if (StringEqualN_IgnoreCase(level, "notices", len))
172
0
    {
173
0
        return LOG_LEVEL_NOTICE;
174
0
    }
175
0
    if (StringEqualN_IgnoreCase(level, "information", len))
176
0
    {
177
0
        return LOG_LEVEL_INFO;
178
0
    }
179
0
    if (StringEqualN_IgnoreCase(level, "verbose", len))
180
0
    {
181
0
        return LOG_LEVEL_VERBOSE;
182
0
    }
183
0
    if (StringEqualN_IgnoreCase(level, "debug", len))
184
0
    {
185
0
        return LOG_LEVEL_DEBUG;
186
0
    }
187
0
    return LOG_LEVEL_NOTHING;
188
0
}
189
190
static const char *LogLevelToColor(LogLevel level)
191
0
{
192
193
0
    switch (level)
194
0
    {
195
0
    case LOG_LEVEL_CRIT:
196
0
    case LOG_LEVEL_ERR:
197
0
        return "\x1b[31m"; // red
198
199
0
    case LOG_LEVEL_WARNING:
200
0
        return "\x1b[33m"; // yellow
201
202
0
    case LOG_LEVEL_NOTICE:
203
0
    case LOG_LEVEL_INFO:
204
0
        return "\x1b[32m"; // green
205
206
0
    case LOG_LEVEL_VERBOSE:
207
0
    case LOG_LEVEL_DEBUG:
208
0
        return "\x1b[34m"; // blue
209
210
0
    default:
211
0
        ProgrammingError("LogLevelToColor: Unexpected log level %d", level);
212
0
    }
213
0
}
214
215
bool LoggingFormatTimestamp(char dest[64], size_t n, struct tm *timestamp)
216
0
{
217
0
    if (strftime(dest, n, "%Y-%m-%dT%H:%M:%S%z", timestamp) == 0)
218
0
    {
219
0
        strlcpy(dest, "<unknown>", n);
220
0
        return false;
221
0
    }
222
0
    return true;
223
0
}
224
225
static void LogToConsole(const char *msg, LogLevel level, bool color)
226
0
{
227
0
    struct tm now;
228
0
    time_t now_seconds = time(NULL);
229
0
    localtime_r(&now_seconds, &now);
230
231
0
    if (color)
232
0
    {
233
0
        fprintf(stdout, "%s", LogLevelToColor(level));
234
0
    }
235
0
    if (level >= LOG_LEVEL_INFO && VPREFIX[0])
236
0
    {
237
0
        fprintf(stdout, "%s ", VPREFIX);
238
0
    }
239
0
    if (TIMESTAMPS)
240
0
    {
241
0
        char formatted_timestamp[64];
242
0
        LoggingFormatTimestamp(formatted_timestamp, 64, &now);
243
0
        fprintf(stdout, "%s ", formatted_timestamp);
244
0
    }
245
246
0
    fprintf(stdout, "%8s: %s\n", LogLevelToString(level), msg);
247
248
0
    if (color)
249
0
    {
250
        // Turn off the color again.
251
0
        fprintf(stdout, "\x1b[0m");
252
0
    }
253
254
0
    fflush(stdout);
255
0
}
256
257
#if !defined(__MINGW32__)
258
static int LogLevelToSyslogPriority(LogLevel level)
259
0
{
260
0
    switch (level)
261
0
    {
262
0
    case LOG_LEVEL_CRIT: return LOG_CRIT;
263
0
    case LOG_LEVEL_ERR: return LOG_ERR;
264
0
    case LOG_LEVEL_WARNING: return LOG_WARNING;
265
0
    case LOG_LEVEL_NOTICE: return LOG_NOTICE;
266
0
    case LOG_LEVEL_INFO: return LOG_INFO;
267
0
    case LOG_LEVEL_VERBOSE: return LOG_DEBUG; /* FIXME: Do we really want to conflate those levels? */
268
0
    case LOG_LEVEL_DEBUG: return LOG_DEBUG;
269
0
    default:
270
0
        ProgrammingError("LogLevelToSyslogPriority: Unexpected log level %d",
271
0
                         level);
272
0
    }
273
274
0
}
275
276
void LogToSystemLog(const char *msg, LogLevel level)
277
0
{
278
0
    char logmsg[4096];
279
0
    snprintf(logmsg, sizeof(logmsg), "CFEngine(%s) %s %s\n", AgentType, VPREFIX, msg);
280
0
    syslog(LogLevelToSyslogPriority(level), "%s", logmsg);
281
0
}
282
#endif  /* !__MINGW32__ */
283
284
#ifndef __MINGW32__
285
const char *GetErrorStrFromCode(int error_code)
286
0
{
287
0
    return strerror(error_code);
288
0
}
289
290
const char *GetErrorStr(void)
291
0
{
292
0
    return strerror(errno);
293
0
}
294
295
#else
296
297
const char *GetErrorStrFromCode(int error_code)
298
{
299
    static char errbuf[CF_BUFSIZE];
300
    int len;
301
302
    memset(errbuf, 0, sizeof(errbuf));
303
304
    if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, error_code,
305
                  MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), errbuf, CF_BUFSIZE, NULL))
306
    {
307
        // remove CRLF from end
308
        len = strlen(errbuf);
309
        errbuf[len - 2] = errbuf[len - 1] = '\0';
310
    } else {
311
        strcpy(errbuf, "Unknown error");
312
    }
313
314
    return errbuf;
315
}
316
317
const char *GetErrorStr(void)
318
{
319
    return GetErrorStrFromCode(GetLastError());
320
}
321
#endif  /* !__MINGW32__ */
322
323
bool WouldLog(LogLevel level)
324
0
{
325
0
    LoggingContext *lctx = GetCurrentThreadContext();
326
327
0
    bool log_to_console = (level <= lctx->report_level);
328
0
    bool log_to_syslog  = (level <= lctx->log_level &&
329
0
                           level < LOG_LEVEL_VERBOSE);
330
0
    bool force_hook     = (lctx->pctx &&
331
0
                           lctx->pctx->log_hook &&
332
0
                           lctx->pctx->force_hook_level >= level);
333
334
0
    return (log_to_console || log_to_syslog || force_hook);
335
0
}
336
337
/**
338
 * #no_format determines whether #fmt_msg is interpreted as a format string and
339
 * combined with #ap to create the real log message (%false) or as a log message
340
 * dirrectly (%true) in which case #ap is ignored.
341
 *
342
 * @see LogNoFormat()
343
 */
344
static void VLogNoFormat(LogLevel level, const char *fmt_msg, va_list ap, bool no_format)
345
0
{
346
0
    LoggingContext *lctx = GetCurrentThreadContext();
347
348
0
    bool log_to_console = ( level <= lctx->report_level );
349
0
    bool log_to_syslog  = ( level <= lctx->log_level &&
350
0
                            level < LOG_LEVEL_VERBOSE );
351
0
    bool force_hook     = ( lctx->pctx &&
352
0
                            lctx->pctx->log_hook &&
353
0
                            lctx->pctx->force_hook_level >= level );
354
355
    /* NEEDS TO BE IN SYNC WITH THE CONDITION IN WouldLog() ABOVE! */
356
0
    if (!log_to_console && !log_to_syslog && !force_hook)
357
0
    {
358
0
        return;                            /* early return - save resources */
359
0
    }
360
361
0
    char *msg;
362
0
    if (no_format)
363
0
    {
364
0
        msg = xstrdup(fmt_msg);
365
0
    }
366
0
    else
367
0
    {
368
0
        msg = StringVFormat(fmt_msg, ap);
369
0
    }
370
0
    char *hooked_msg = NULL;
371
372
0
    if (logging_into_buffer &&
373
0
        (level >= min_log_into_buffer_level) && (level <= max_log_into_buffer_level))
374
0
    {
375
0
        assert(log_buffer != NULL);
376
377
0
        if (log_buffer == NULL)
378
0
        {
379
            /* Should never happen. */
380
0
            Log(LOG_LEVEL_ERR,
381
0
                "Attempt to log a message to an unitialized log buffer, discarding the message");
382
0
        }
383
384
0
        LogEntry *entry = xmalloc(sizeof(LogEntry));
385
0
        entry->level = level;
386
0
        entry->msg = msg;
387
388
0
        SeqAppend(log_buffer, entry);
389
0
        return;
390
0
    }
391
392
    /* Remove ending EOLN. */
393
0
    for (char *sp = msg; *sp != '\0'; sp++)
394
0
    {
395
0
        if (*sp == '\n' && *(sp+1) == '\0')
396
0
        {
397
0
            *sp = '\0';
398
0
            break;
399
0
        }
400
0
    }
401
402
0
    if (lctx->pctx && lctx->pctx->log_hook)
403
0
    {
404
0
        hooked_msg = lctx->pctx->log_hook(lctx->pctx, level, msg);
405
0
    }
406
0
    else
407
0
    {
408
0
        hooked_msg = msg;
409
0
    }
410
411
0
    if (log_to_console)
412
0
    {
413
0
        LogToConsole(hooked_msg, level, lctx->color);
414
0
    }
415
0
    if (log_to_syslog)
416
0
    {
417
0
        LogToSystemLog(hooked_msg, level);
418
0
    }
419
420
0
    if (hooked_msg != msg)
421
0
    {
422
0
        free(hooked_msg);
423
0
    }
424
0
    free(msg);
425
0
}
426
427
void VLog(LogLevel level, const char *fmt, va_list ap)
428
0
{
429
0
    VLogNoFormat(level, fmt, ap, false);
430
0
}
431
432
/**
433
 * @brief Logs binary data in #buf, with unprintable bytes translated to '.'.
434
 *        Message is prefixed with #prefix.
435
 * @param #buflen must be no more than CF_BUFSIZE
436
 */
437
void LogRaw(LogLevel level, const char *prefix, const void *buf, size_t buflen)
438
0
{
439
0
    if (buflen > CF_BUFSIZE)
440
0
    {
441
0
        debug_abort_if_reached();
442
0
        buflen = CF_BUFSIZE;
443
0
    }
444
445
0
    LoggingContext *lctx = GetCurrentThreadContext();
446
0
    if (level <= lctx->report_level || level <= lctx->log_level)
447
0
    {
448
0
        const unsigned char *src = buf;
449
0
        unsigned char dst[CF_BUFSIZE+1];
450
0
        assert(buflen < sizeof(dst));
451
0
        size_t i;
452
0
        for (i = 0; i < buflen; i++)
453
0
        {
454
0
            dst[i] = isprint(src[i]) ? src[i] : '.';
455
0
        }
456
0
        assert(i < sizeof(dst));
457
0
        dst[i] = '\0';
458
459
        /* And Log the translated buffer, which is now a valid string. */
460
0
        Log(level, "%s%s", prefix, dst);
461
0
    }
462
0
}
463
464
void Log(LogLevel level, const char *fmt, ...)
465
0
{
466
0
    va_list ap;
467
0
    va_start(ap, fmt);
468
0
    VLog(level, fmt, ap);
469
0
    va_end(ap);
470
0
}
471
472
/**
473
 * The same as above, but for logging messages without formatting sequences
474
 * ("%s", "%d",...). Or more precisely, for *safe* logging of messages that may
475
 * contain such sequences (for example Log(LOG_LEVEL_ERR, "%s") can potentially
476
 * log some secrets).
477
 *
478
 * It doesn't have the FUNC_ATTR_PRINTF restriction that causes a compilation
479
 * warning/error if #msg is not a constant string.
480
 *
481
 * @note This is for special cases where #msg was already printf-like formatted,
482
 *       the variadic arguments are ignored, they are here just so that
483
 *       'va_list ap' can be constructed and passed to VLogNoFormat().
484
 *
485
 * @see CommitLogBuffer()
486
 */
487
static void LogNoFormat(LogLevel level, const char *msg, ...)
488
0
{
489
0
    va_list ap;
490
0
    va_start(ap, msg);
491
0
    VLogNoFormat(level, msg, ap, true);
492
0
    va_end(ap);
493
0
}
494
495
static bool module_is_enabled[LOG_MOD_MAX];
496
static const char *log_modules[LOG_MOD_MAX] =
497
{
498
    "",
499
    "evalctx",
500
    "expand",
501
    "iterations",
502
    "parser",
503
    "vartable",
504
    "vars",
505
    "locks",
506
    "ps",
507
};
508
509
static enum LogModule LogModuleFromString(const char *s)
510
0
{
511
0
    for (enum LogModule i = 0; i < LOG_MOD_MAX; i++)
512
0
    {
513
0
        if (strcmp(log_modules[i], s) == 0)
514
0
        {
515
0
            return i;
516
0
        }
517
0
    }
518
519
0
    return LOG_MOD_NONE;
520
0
}
521
522
void LogEnableModule(enum LogModule mod)
523
0
{
524
0
    assert(mod < LOG_MOD_MAX);
525
526
0
    module_is_enabled[mod] = true;
527
0
}
528
529
void LogModuleHelp(void)
530
0
{
531
0
    printf("\n--log-modules accepts a comma separated list of one or more of the following:\n\n");
532
0
    printf("    help\n");
533
0
    printf("    all\n");
534
0
    for (enum LogModule i = LOG_MOD_NONE + 1;  i < LOG_MOD_MAX;  i++)
535
0
    {
536
0
        printf("    %s\n", log_modules[i]);
537
0
    }
538
0
    printf("\n");
539
0
}
540
541
/**
542
 * Parse a string of modules, and enable the relevant DEBUG logging modules.
543
 * Example strings:
544
 *
545
 *   all         : enables all debug modules
546
 *   help        : enables nothing, but prints a help message
547
 *   iterctx     : enables the "iterctx" debug logging module
548
 *   iterctx,vars: enables the 2 debug modules, "iterctx" and "vars"
549
 *
550
 * @NOTE modifies string #s but restores it before returning.
551
 */
552
bool LogEnableModulesFromString(char *s)
553
0
{
554
0
    bool retval = true;
555
556
0
    const char *token = s;
557
0
    char saved_sep = ',';                     /* any non-NULL value will do */
558
0
    while (saved_sep != '\0' && retval != false)
559
0
    {
560
0
        char *next_token = strchrnul(token, ',');
561
0
        saved_sep        = *next_token;
562
0
        *next_token      = '\0';                      /* modify parameter s */
563
0
        size_t token_len = next_token - token;
564
565
0
        if (strcmp(token, "help") == 0)
566
0
        {
567
0
            LogModuleHelp();
568
0
            retval = false;                                   /* early exit */
569
0
        }
570
0
        else if (strcmp(token, "all") == 0)
571
0
        {
572
0
            for (enum LogModule j = LOG_MOD_NONE + 1; j < LOG_MOD_MAX; j++)
573
0
            {
574
0
                LogEnableModule(j);
575
0
            }
576
0
        }
577
0
        else
578
0
        {
579
0
            enum LogModule mod = LogModuleFromString(token);
580
581
0
            assert(mod < LOG_MOD_MAX);
582
0
            if (mod == LOG_MOD_NONE)
583
0
            {
584
0
                Log(LOG_LEVEL_WARNING,
585
0
                    "Unknown debug logging module '%*s'",
586
0
                    (int) token_len, token);
587
0
            }
588
0
            else
589
0
            {
590
0
                LogEnableModule(mod);
591
0
            }
592
0
        }
593
594
595
0
        *next_token = saved_sep;            /* restore modified parameter s */
596
0
        next_token++;                       /* bypass comma */
597
0
        token = next_token;
598
0
    }
599
600
0
    return retval;
601
0
}
602
603
bool LogModuleEnabled(enum LogModule mod)
604
0
{
605
0
    assert(mod > LOG_MOD_NONE);
606
0
    assert(mod < LOG_MOD_MAX);
607
608
0
    if (module_is_enabled[mod])
609
0
    {
610
0
        return true;
611
0
    }
612
0
    else
613
0
    {
614
0
        return false;
615
0
    }
616
0
}
617
618
void LogDebug(enum LogModule mod, const char *fmt, ...)
619
0
{
620
0
    assert(mod < LOG_MOD_MAX);
621
622
    /* Did we forget any entry in log_modules? */
623
0
    nt_static_assert(sizeof(log_modules) / sizeof(log_modules[0]) == LOG_MOD_MAX);
624
625
0
    if (LogModuleEnabled(mod))
626
0
    {
627
0
        va_list ap;
628
0
        va_start(ap, fmt);
629
0
        VLog(LOG_LEVEL_DEBUG, fmt, ap);
630
0
        va_end(ap);
631
        /* VLog(LOG_LEVEL_DEBUG, "%s: ...", */
632
        /*      debug_modules_description[mod_order], ...); */
633
0
    }
634
0
}
635
636
637
void LogSetGlobalLevel(LogLevel level)
638
0
{
639
0
    global_level = level;
640
0
    if (global_system_log_level == LOG_LEVEL_NOTHING)
641
0
    {
642
0
        LoggingPrivSetLevels(level, level);
643
0
    }
644
0
    else
645
0
    {
646
0
        LoggingPrivSetLevels(global_system_log_level, level);
647
0
    }
648
0
}
649
650
void LogSetGlobalLevelArgOrExit(const char *const arg)
651
0
{
652
0
    LogLevel level = LogLevelFromString(arg);
653
0
    if (level == LOG_LEVEL_NOTHING)
654
0
    {
655
        // This function is used as part of initializing the logging
656
        // system. Using Log() can be considered incorrect, even though
657
        // it may "work". Let's just print an error to stderr:
658
0
        fprintf(stderr, "Invalid log level: '%s'\n", arg);
659
0
        DoCleanupAndExit(1);
660
0
    }
661
0
    LogSetGlobalLevel(level);
662
0
}
663
664
LogLevel LogGetGlobalLevel(void)
665
0
{
666
0
    return global_level;
667
0
}
668
669
void LogSetGlobalSystemLogLevel(LogLevel level)
670
0
{
671
    /* LOG_LEVEL_NOTHING means "unset" (see LogUnsetGlobalSystemLogLevel()) */
672
0
    assert(level != LOG_LEVEL_NOTHING);
673
674
0
    global_system_log_level = level;
675
0
    LoggingPrivSetLevels(level, global_level);
676
0
}
677
678
void LogUnsetGlobalSystemLogLevel(void)
679
0
{
680
0
    global_system_log_level = LOG_LEVEL_NOTHING;
681
0
    LoggingPrivSetLevels(global_level, global_level);
682
0
}
683
684
LogLevel LogGetGlobalSystemLogLevel(void)
685
0
{
686
0
    return global_system_log_level;
687
0
}
688
689
void LoggingSetColor(bool enabled)
690
0
{
691
0
    LoggingContext *lctx = GetCurrentThreadContext();
692
0
    lctx->color = enabled;
693
0
}
694
695
// byte_magnitude and byte_unit are used to print human readable byte counts
696
long byte_magnitude(long bytes)
697
0
{
698
0
    const long Ki = 1024;
699
0
    const long Mi = Ki * 1024;
700
0
    const long Gi = Mi * 1024;
701
702
0
    if (bytes > 8 * Gi)
703
0
    {
704
0
        return bytes / Gi;
705
0
    }
706
0
    else if (bytes > 8 * Mi)
707
0
    {
708
0
        return bytes / Mi;
709
0
    }
710
0
    else if (bytes > 8 * Ki)
711
0
    {
712
0
        return bytes / Ki;
713
0
    }
714
0
    return bytes;
715
0
}
716
717
// Use this with byte_magnitude
718
// Note that the cutoff is at 8x unit, because 3192 bytes is arguably more
719
// useful than 3KiB
720
const char *byte_unit(long bytes)
721
0
{
722
0
    const long Ki = 1024;
723
0
    const long Mi = Ki * 1024;
724
0
    const long Gi = Mi * 1024;
725
726
0
    if (bytes > 8 * Gi)
727
0
    {
728
0
        return "GiB";
729
0
    }
730
0
    else if (bytes > 8 * Mi)
731
0
    {
732
0
        return "MiB";
733
0
    }
734
0
    else if (bytes > 8 * Ki)
735
0
    {
736
0
        return "KiB";
737
0
    }
738
0
    return "bytes";
739
0
}
740
741
void LogEntryDestroy(LogEntry *entry)
742
0
{
743
0
    if (entry != NULL)
744
0
    {
745
0
        free(entry->msg);
746
0
        free(entry);
747
0
    }
748
0
}
749
750
void StartLoggingIntoBuffer(LogLevel min_level, LogLevel max_level)
751
0
{
752
0
    assert((log_buffer == NULL) && (!logging_into_buffer));
753
754
0
    if (log_buffer != NULL)
755
0
    {
756
        /* Should never happen. */
757
0
        Log(LOG_LEVEL_ERR, "Re-initializing log buffer without prior commit, discarding messages");
758
0
        DiscardLogBuffer();
759
0
    }
760
761
0
    log_buffer = SeqNew(16, LogEntryDestroy);
762
0
    logging_into_buffer = true;
763
0
    min_log_into_buffer_level = min_level;
764
0
    max_log_into_buffer_level = max_level;
765
0
}
766
767
void DiscardLogBuffer()
768
0
{
769
0
    SeqDestroy(log_buffer);
770
0
    log_buffer = NULL;
771
0
    logging_into_buffer = false;
772
0
}
773
774
void CommitLogBuffer()
775
0
{
776
0
    assert(logging_into_buffer);
777
0
    assert(log_buffer != NULL);
778
779
0
    if (log_buffer == NULL)
780
0
    {
781
        /* Should never happen. */
782
0
        Log(LOG_LEVEL_ERR, "Attempt to commit an unitialized log buffer");
783
0
    }
784
785
    /* Disable now so that LogNonConstant() below doesn't append the message
786
     * into the buffer instaed of logging it. */
787
0
    logging_into_buffer = false;
788
789
0
    const size_t n_entries = SeqLength(log_buffer);
790
0
    for (size_t i = 0; i < n_entries; i++)
791
0
    {
792
0
        LogEntry *entry = SeqAt(log_buffer, i);
793
0
        LogNoFormat(entry->level, entry->msg);
794
0
    }
795
796
0
    DiscardLogBuffer();
797
0
}