Coverage Report

Created: 2025-10-10 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/open5gs/lib/core/ogs-log.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
3
 *
4
 * This file is part of Open5GS.
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Affero General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
#include "core-config-private.h"
21
22
#if HAVE_CTYPE_H
23
#include <ctype.h>
24
#endif
25
26
#if HAVE_STDARG_H
27
#include <stdarg.h>
28
#endif
29
30
#include "ogs-core.h"
31
32
28.6k
#define TA_NOR              "\033[0m"       /* all off */
33
34
#define TA_FGC_BLACK        "\033[30m"      /* Black */
35
#define TA_FGC_RED          "\033[31m"      /* Red */
36
7.16k
#define TA_FGC_BOLD_RED     "\033[1;31m"    /* Bold Red */
37
7.16k
#define TA_FGC_GREEN        "\033[32m"      /* Green */
38
7.16k
#define TA_FGC_BOLD_GREEN   "\033[1;32m"    /* Bold Green */
39
7.16k
#define TA_FGC_YELLOW       "\033[33m"      /* Yellow */
40
7.16k
#define TA_FGC_BOLD_YELLOW  "\033[1;33m"    /* Bold Yellow */
41
#define TA_FGC_BOLD_BLUE    "\033[1;34m"    /* Bold Blue */
42
#define TA_FGC_BOLD_MAGENTA "\033[1;35m"    /* Bold Magenta */
43
7.16k
#define TA_FGC_BOLD_CYAN    "\033[1;36m"    /* Bold Cyan */
44
7.16k
#define TA_FGC_WHITE        "\033[37m"      /* White  */
45
7.16k
#define TA_FGC_BOLD_WHITE   "\033[1;37m"    /* Bold White  */
46
#define TA_FGC_DEFAULT      "\033[39m"      /* default */
47
48
typedef enum {
49
    OGS_LOG_STDERR_TYPE,
50
    OGS_LOG_FILE_TYPE,
51
} ogs_log_type_e;
52
53
typedef struct ogs_log_s {
54
    ogs_lnode_t node;
55
56
    ogs_log_type_e type;
57
58
    union {
59
        struct {
60
            FILE *out;
61
            const char *name;
62
        } file;
63
    };
64
65
    struct {
66
    ED7(uint8_t color:1;,
67
        uint8_t timestamp:1;,
68
        uint8_t domain:1;,
69
        uint8_t level:1;,
70
        uint8_t fileline:1;,
71
        uint8_t function:1;,
72
        uint8_t linefeed:1;)
73
    } print;
74
75
    void (*writer)(ogs_log_t *log, ogs_log_level_e level, const char *string);
76
77
} ogs_log_t;
78
79
typedef struct ogs_log_domain_s {
80
    ogs_lnode_t node;
81
82
    int id;
83
    ogs_log_level_e level;
84
    const char *name;
85
} ogs_log_domain_t;
86
87
const char *level_strings[] = {
88
    NULL,
89
    "FATAL", "ERROR", "WARNING", "INFO", "DEBUG", "TRACE",
90
};
91
92
static OGS_POOL(log_pool, ogs_log_t);
93
static OGS_LIST(log_list);
94
95
static OGS_POOL(domain_pool, ogs_log_domain_t);
96
static OGS_LIST(domain_list);
97
98
static ogs_log_t *add_log(ogs_log_type_e type);
99
static int file_cycle(ogs_log_t *log);
100
101
static char *log_timestamp(char *buf, char *last,
102
        int use_color);
103
static char *log_domain(char *buf, char *last,
104
        const char *name, int use_color);
105
static char *log_content(char *buf, char *last,
106
        const char *format, va_list ap);
107
static char *log_level(char *buf, char *last,
108
        ogs_log_level_e level, int use_color);
109
static char *log_linefeed(char *buf, char *last);
110
111
static void file_writer(
112
        ogs_log_t *log, ogs_log_level_e level, const char *string);
113
114
void ogs_log_init(void)
115
2
{
116
2
    ogs_pool_init(&log_pool, ogs_core()->log.pool);
117
2
    ogs_pool_init(&domain_pool, ogs_core()->log.domain_pool);
118
119
2
    ogs_log_add_domain("core", ogs_core()->log.level);
120
2
    ogs_log_add_stderr();
121
2
}
122
123
void ogs_log_final(void)
124
0
{
125
0
    ogs_log_t *log, *saved_log;
126
0
    ogs_log_domain_t *domain, *saved_domain;
127
128
0
    ogs_list_for_each_safe(&log_list, saved_log, log)
129
0
        ogs_log_remove(log);
130
0
    ogs_pool_final(&log_pool);
131
132
0
    ogs_list_for_each_safe(&domain_list, saved_domain, domain)
133
0
        ogs_log_remove_domain(domain);
134
0
    ogs_pool_final(&domain_pool);
135
0
}
136
137
void ogs_log_cycle(void)
138
0
{
139
0
    ogs_log_t *log = NULL;
140
141
0
    ogs_list_for_each(&log_list, log) {
142
0
        switch(log->type) {
143
0
        case OGS_LOG_FILE_TYPE:
144
0
            file_cycle(log);
145
0
        default:
146
0
            break;
147
0
        }
148
0
    }
149
0
}
150
151
ogs_log_t *ogs_log_add_stderr(void)
152
2
{
153
2
    ogs_log_t *log = NULL;
154
    
155
2
    log = add_log(OGS_LOG_STDERR_TYPE);
156
2
    ogs_assert(log);
157
158
2
    log->file.out = stderr;
159
2
    log->writer = file_writer;
160
161
2
#if !defined(_WIN32)
162
2
    log->print.color = 1;
163
2
#endif
164
165
2
    return log;
166
2
}
167
168
ogs_log_t *ogs_log_add_file(const char *name)
169
0
{
170
0
    FILE *out = NULL;
171
0
    ogs_log_t *log = NULL;
172
173
0
    out = fopen(name, "a");
174
0
    if (!out) 
175
0
        return NULL;
176
    
177
0
    log = add_log(OGS_LOG_FILE_TYPE);
178
0
    ogs_assert(log);
179
180
0
    log->file.name = name;
181
0
    log->file.out = out;
182
183
0
    log->writer = file_writer;
184
185
0
    return log;
186
0
}
187
188
void ogs_log_remove(ogs_log_t *log)
189
0
{
190
0
    ogs_assert(log);
191
192
0
    ogs_list_remove(&log_list, log);
193
194
0
    if (log->type == OGS_LOG_FILE_TYPE) {
195
0
        ogs_assert(log->file.out);
196
0
        fclose(log->file.out);
197
0
        log->file.out = NULL;
198
0
    }
199
200
0
    ogs_pool_free(&log_pool, log);
201
0
}
202
203
ogs_log_domain_t *ogs_log_add_domain(const char *name, ogs_log_level_e level)
204
14
{
205
14
    ogs_log_domain_t *domain = NULL;
206
207
14
    ogs_assert(name);
208
209
14
    ogs_pool_alloc(&domain_pool, &domain);
210
14
    ogs_assert(domain);
211
212
14
    domain->name = name;
213
14
    domain->id = ogs_pool_index(&domain_pool, domain);
214
14
    domain->level = level;
215
216
14
    ogs_list_add(&domain_list, domain);
217
218
14
    return domain;
219
14
}
220
221
ogs_log_domain_t *ogs_log_find_domain(const char *name)
222
13
{
223
13
    ogs_log_domain_t *domain = NULL;
224
225
13
    ogs_assert(name);
226
227
13
    ogs_list_for_each(&domain_list, domain)
228
48
        if (!ogs_strcasecmp(domain->name, name))
229
1
            return domain;
230
231
12
    return NULL;
232
13
}
233
234
void ogs_log_remove_domain(ogs_log_domain_t *domain)
235
0
{
236
0
    ogs_assert(domain);
237
238
0
    ogs_list_remove(&domain_list, domain);
239
0
    ogs_pool_free(&domain_pool, domain);
240
0
}
241
242
void ogs_log_set_domain_level(int id, ogs_log_level_e level)
243
1
{
244
1
    ogs_log_domain_t *domain = NULL;
245
246
1
    ogs_assert(id > 0 && id <= ogs_core()->log.domain_pool);
247
248
1
    domain = ogs_pool_find(&domain_pool, id);
249
1
    ogs_assert(domain);
250
251
1
    domain->level = level;
252
1
}
253
254
ogs_log_level_e ogs_log_get_domain_level(int id)
255
0
{
256
0
    ogs_log_domain_t *domain = NULL;
257
258
0
    ogs_assert(id > 0 && id <= ogs_core()->log.domain_pool);
259
260
0
    domain = ogs_pool_find(&domain_pool, id);
261
0
    ogs_assert(domain);
262
263
0
    return domain->level;
264
0
}
265
266
const char *ogs_log_get_domain_name(int id)
267
0
{
268
0
    ogs_log_domain_t *domain = NULL;
269
270
0
    ogs_assert(id > 0 && id <= ogs_core()->log.domain_pool);
271
272
0
    domain = ogs_pool_find(&domain_pool, id);
273
0
    ogs_assert(domain);
274
275
0
    return domain->name;
276
0
}
277
278
int ogs_log_get_domain_id(const char *name)
279
0
{
280
0
    ogs_log_domain_t *domain = NULL;
281
282
0
    ogs_assert(name);
283
    
284
0
    domain = ogs_log_find_domain(name);
285
0
    ogs_assert(domain);
286
287
0
    return domain->id;
288
0
}
289
290
void ogs_log_install_domain(int *domain_id,
291
        const char *name, ogs_log_level_e level)
292
13
{
293
13
    ogs_log_domain_t *domain = NULL;
294
295
13
    ogs_assert(domain_id);
296
13
    ogs_assert(name);
297
298
13
    domain = ogs_log_find_domain(name);
299
13
    if (domain) {
300
1
        ogs_warn("`%s` log-domain duplicated", name);
301
1
        if (level != domain->level) {
302
1
            ogs_warn("[%s]->[%s] log-level changed",
303
1
                    level_strings[domain->level], level_strings[level]);
304
1
            ogs_log_set_domain_level(domain->id, level);
305
1
        }
306
12
    } else {
307
12
        domain = ogs_log_add_domain(name, level);
308
12
        ogs_assert(domain);
309
12
    }
310
311
13
    *domain_id = domain->id;
312
13
}
313
314
void ogs_log_set_mask_level(const char *_mask, ogs_log_level_e level)
315
0
{
316
0
    ogs_log_domain_t *domain = NULL;
317
318
0
    if (_mask) {
319
0
        const char *delim = " \t\n,:";
320
0
        char *mask = NULL;
321
0
        char *saveptr;
322
0
        char *name;
323
324
0
        mask = ogs_strdup(_mask);
325
0
        ogs_assert(mask);
326
327
0
        for (name = ogs_strtok_r(mask, delim, &saveptr);
328
0
            name != NULL;
329
0
            name = ogs_strtok_r(NULL, delim, &saveptr)) {
330
331
0
            domain = ogs_log_find_domain(name);
332
0
            if (domain)
333
0
                domain->level = level;
334
0
        }
335
336
0
        ogs_free(mask);
337
0
    } else {
338
0
        ogs_list_for_each(&domain_list, domain)
339
0
            domain->level = level;
340
0
    }
341
0
}
342
343
void ogs_log_set_timestamp(ogs_log_ts_e ts_default, ogs_log_ts_e ts_file)
344
0
{
345
0
    ogs_log_t *log;
346
347
0
    if (ts_default == OGS_LOG_TS_UNSET)
348
0
        ts_default = OGS_LOG_TS_ENABLED;
349
350
0
    if (ts_file == OGS_LOG_TS_UNSET)
351
0
        ts_file = ts_default;
352
353
0
    ogs_list_for_each(&log_list, log) {
354
0
        switch (log->type) {
355
0
            case OGS_LOG_FILE_TYPE:
356
0
                log->print.timestamp = (ts_file == OGS_LOG_TS_ENABLED);
357
0
                break;
358
0
            default:
359
0
                log->print.timestamp = (ts_default == OGS_LOG_TS_ENABLED);
360
0
                break;
361
0
        }
362
0
    }
363
0
}
364
365
static ogs_log_level_e ogs_log_level_from_string(const char *string)
366
0
{
367
0
    ogs_log_level_e level = OGS_ERROR;
368
369
0
    if (!strcasecmp(string, "none")) level = OGS_LOG_NONE;
370
0
    else if (!strcasecmp(string, "fatal")) level = OGS_LOG_FATAL;
371
0
    else if (!strcasecmp(string, "error")) level = OGS_LOG_ERROR;
372
0
    else if (!strcasecmp(string, "warn")) level = OGS_LOG_WARN;
373
0
    else if (!strcasecmp(string, "info")) level = OGS_LOG_INFO;
374
0
    else if (!strcasecmp(string, "debug")) level = OGS_LOG_DEBUG;
375
0
    else if (!strcasecmp(string, "trace")) level = OGS_LOG_TRACE;
376
377
0
    return level;
378
0
}
379
380
int ogs_log_config_domain(const char *domain, const char *level)
381
0
{
382
0
    if (domain || level) {
383
0
        int l = ogs_core()->log.level;
384
385
0
        if (level) {
386
0
            l = ogs_log_level_from_string(level);
387
0
            if (l == OGS_ERROR) {
388
0
                ogs_error("Invalid LOG-LEVEL "
389
0
                        "[none:fatal|error|warn|info|debug|trace]: %s\n",
390
0
                        level);
391
0
                return OGS_ERROR;
392
0
            }
393
0
        }
394
395
0
        ogs_log_set_mask_level(domain, l);
396
0
    }
397
398
0
    return OGS_OK;
399
0
}
400
401
void ogs_log_vprintf(ogs_log_level_e level, int id,
402
    ogs_err_t err, const char *file, int line, const char *func,
403
    int content_only, const char *format, va_list ap)
404
279k
{
405
279k
    ogs_log_t *log = NULL;
406
279k
    ogs_log_domain_t *domain = NULL;
407
408
279k
    char logstr[OGS_HUGE_LEN];
409
279k
    char *p, *last;
410
411
279k
    int wrote_stderr = 0;
412
413
279k
    ogs_list_for_each(&log_list, log) {
414
279k
        domain = ogs_pool_find(&domain_pool, id);
415
279k
        if (!domain) {
416
0
            fprintf(stderr, "No LogDomain[id:%d] in %s:%d", id, file, line);
417
0
            ogs_assert_if_reached();
418
0
        }
419
279k
        if (domain->level < level)
420
272k
            return;
421
422
7.32k
        p = logstr;
423
7.32k
        last = logstr + OGS_HUGE_LEN;
424
425
7.32k
        if (!content_only) {
426
7.16k
            if (log->print.timestamp)
427
7.16k
                p = log_timestamp(p, last, log->print.color);
428
7.16k
            if (log->print.domain)
429
7.16k
                p = log_domain(p, last, domain->name, log->print.color);
430
7.16k
            if (log->print.level)
431
7.16k
                p = log_level(p, last, level, log->print.color);
432
7.16k
        }
433
434
7.32k
        p = log_content(p, last, format, ap);
435
436
7.32k
        if (err) {
437
0
            char errbuf[OGS_HUGE_LEN];
438
0
            p = ogs_slprintf(p, last, " (%d:%s)",
439
0
                    (int)err, ogs_strerror(err, errbuf, OGS_HUGE_LEN));
440
0
        }
441
442
7.32k
        if (!content_only) {
443
7.16k
            if (log->print.fileline)
444
7.16k
                p = ogs_slprintf(p, last, " (%s:%d)", file, line);
445
7.16k
            if (log->print.function)
446
0
                p = ogs_slprintf(p, last, " %s()", func);
447
7.16k
            if (log->print.linefeed) 
448
7.16k
                p = log_linefeed(p, last);
449
7.16k
        }
450
451
7.32k
        log->writer(log, level, logstr);
452
        
453
7.32k
        if (log->type == OGS_LOG_STDERR_TYPE)
454
7.32k
            wrote_stderr = 1;
455
7.32k
    }
456
457
7.32k
    if (!wrote_stderr)
458
0
    {
459
0
        int use_color = 0;
460
0
#if !defined(_WIN32)
461
0
        use_color = 1;
462
0
#endif
463
464
0
        p = logstr;
465
0
        last = logstr + OGS_HUGE_LEN;
466
467
0
        if (!content_only) {
468
0
            p = log_timestamp(p, last, use_color);
469
0
            p = log_level(p, last, level, use_color);
470
0
        }
471
0
        p = log_content(p, last, format, ap);
472
0
        if (!content_only) {
473
0
            p = ogs_slprintf(p, last, " (%s:%d)", file, line);
474
0
            p = ogs_slprintf(p, last, " %s()", func);
475
0
            p = log_linefeed(p, last);
476
0
        }
477
478
0
        fprintf(stderr, "%s", logstr);
479
0
        fflush(stderr);
480
0
    }
481
7.32k
}
482
483
void ogs_log_printf(ogs_log_level_e level, int id,
484
    ogs_err_t err, const char *file, int line, const char *func,
485
    int content_only, const char *format, ...)
486
279k
{
487
279k
    va_list args;
488
489
279k
    va_start(args, format);
490
279k
    ogs_log_vprintf(level, id,
491
279k
            err, file, line, func, content_only, format, args);
492
279k
    va_end(args);
493
279k
}
494
495
void ogs_log_hexdump_func(ogs_log_level_e level, int id,
496
        const unsigned char *data, size_t len)
497
85.8k
{
498
85.8k
    size_t n, m;
499
85.8k
    char dumpstr[OGS_HUGE_LEN];
500
85.8k
    char *p, *last;
501
502
85.8k
    last = dumpstr + OGS_HUGE_LEN;
503
85.8k
    p = dumpstr;
504
505
172k
    for (n = 0; n < len; n += 16) {
506
86.5k
        p = ogs_slprintf(p, last, "%04x: ", (int)n);
507
        
508
1.47M
        for (m = n; m < n + 16; m++) {
509
1.38M
            if (m > n && (m % 4) == 0)
510
259k
                p = ogs_slprintf(p, last, " ");
511
1.38M
            if (m < len)
512
150k
                p = ogs_slprintf(p, last, "%02x", data[m]);
513
1.23M
            else
514
1.23M
                p = ogs_slprintf(p, last, "  ");
515
1.38M
        }
516
517
86.5k
        p = ogs_slprintf(p, last, "   ");
518
519
236k
        for (m = n; m < len && m < n + 16; m++)
520
150k
            p = ogs_slprintf(p, last, "%c", isprint(data[m]) ? data[m] : '.');
521
522
86.5k
        p = ogs_slprintf(p, last, "\n");
523
86.5k
    }
524
525
85.8k
    ogs_log_print(level, "%s", dumpstr);
526
85.8k
}
527
528
static ogs_log_t *add_log(ogs_log_type_e type)
529
2
{
530
2
    ogs_log_t *log = NULL;
531
532
2
    ogs_pool_alloc(&log_pool, &log);
533
2
    ogs_assert(log);
534
2
    memset(log, 0, sizeof *log);
535
536
2
    log->type = type;
537
538
2
    log->print.timestamp = 1;
539
2
    log->print.domain = 1;
540
2
    log->print.level = 1;
541
2
    log->print.fileline = 1;
542
2
    log->print.linefeed = 1;
543
544
2
    ogs_list_add(&log_list, log);
545
546
2
    return log;
547
2
}
548
549
static int file_cycle(ogs_log_t *log)
550
0
{
551
0
    ogs_assert(log);
552
0
    ogs_assert(log->file.out);
553
0
    ogs_assert(log->file.name);
554
555
0
    fclose(log->file.out);
556
0
    log->file.out = fopen(log->file.name, "a");
557
0
    ogs_assert(log->file.out);
558
559
0
    return 0;
560
0
}
561
562
static char *log_timestamp(char *buf, char *last,
563
        int use_color)
564
7.16k
{
565
7.16k
    struct timeval tv;
566
7.16k
    struct tm tm;
567
7.16k
    char nowstr[32];
568
569
7.16k
    ogs_gettimeofday(&tv);
570
7.16k
    ogs_localtime(tv.tv_sec, &tm);
571
7.16k
    strftime(nowstr, sizeof nowstr, "%m/%d %H:%M:%S", &tm);
572
573
7.16k
    buf = ogs_slprintf(buf, last, "%s%s.%03d%s: ",
574
7.16k
            use_color ? TA_FGC_GREEN : "",
575
7.16k
            nowstr, (int)(tv.tv_usec/1000),
576
7.16k
            use_color ? TA_NOR : "");
577
578
7.16k
    return buf;
579
7.16k
}
580
581
static char *log_domain(char *buf, char *last,
582
        const char *name, int use_color)
583
7.16k
{
584
7.16k
    buf = ogs_slprintf(buf, last, "[%s%s%s] ",
585
7.16k
            use_color ? TA_FGC_YELLOW : "",
586
7.16k
            name,
587
7.16k
            use_color ? TA_NOR : "");
588
589
7.16k
    return buf;
590
7.16k
}
591
592
static char *log_level(char *buf, char *last,
593
        ogs_log_level_e level, int use_color)
594
7.16k
{
595
7.16k
    const char *colors[] = {
596
7.16k
        TA_NOR,
597
7.16k
        TA_FGC_BOLD_RED, TA_FGC_BOLD_YELLOW, TA_FGC_BOLD_CYAN,
598
7.16k
        TA_FGC_BOLD_GREEN, TA_FGC_BOLD_WHITE, TA_FGC_WHITE,
599
7.16k
    };
600
601
7.16k
    buf = ogs_slprintf(buf, last, "%s%s%s: ",
602
7.16k
            use_color ? colors[level] : "",
603
7.16k
            level_strings[level],
604
7.16k
            use_color ? TA_NOR : "");
605
606
7.16k
    return buf;
607
7.16k
}
608
609
static char *log_content(char *buf, char *last,
610
        const char *format, va_list ap)
611
7.32k
{
612
7.32k
    va_list bp;
613
614
7.32k
    va_copy(bp, ap);
615
7.32k
#pragma GCC diagnostic push
616
7.32k
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
617
7.32k
    buf = ogs_vslprintf(buf, last, format, bp);
618
7.32k
#pragma GCC diagnostic pop
619
7.32k
    va_end(bp);
620
621
7.32k
    return buf;
622
7.32k
}
623
624
static char *log_linefeed(char *buf, char *last)
625
7.16k
{
626
#if defined(_WIN32)
627
    if (buf > last - 3)
628
        buf = last - 3;
629
630
    buf = ogs_slprintf(buf, last, "\r\n");
631
#else
632
7.16k
    if (buf > last - 2)
633
0
        buf = last - 2;
634
635
7.16k
    buf = ogs_slprintf(buf, last, "\n");
636
7.16k
#endif
637
638
7.16k
    return buf;
639
7.16k
}
640
641
static void file_writer(
642
        ogs_log_t *log, ogs_log_level_e level, const char *string)
643
7.32k
{
644
7.32k
    fprintf(log->file.out, "%s", string);
645
7.32k
    fflush(log->file.out);
646
7.32k
}
647