Skip to content

Commit 9aa0525

Browse files
add structured JSON logging
Signed-off-by: darkexplosiveqwx <101737077+darkexplosiveqwx@users.noreply.github.com>
1 parent 64010dc commit 9aa0525

16 files changed

Lines changed: 342 additions & 38 deletions

src/api/config.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ cJSON *addJSONConfValue(const enum conf_type conf_type, union conf_value *val)
189189
return cJSON_CreateStringReference(get_temp_unit_str(val->temp_unit));
190190
case CONF_ENUM_BLOCKING_EDNS_MODE:
191191
return cJSON_CreateStringReference(get_edns_mode_str(val->edns_mode));
192+
case CONF_ENUM_LOG_DESTINATION:
193+
return cJSON_CreateStringReference(get_log_destination_str(val->log_destination));
192194
case CONF_STRUCT_IN_ADDR:
193195
{
194196
// Special case 0.0.0.0 -> return empty string
@@ -452,6 +454,19 @@ static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem, struct
452454
log_web_debug(DEBUG_CONFIG, "%s = %d", conf_item->k, conf_item->v.edns_mode);
453455
break;
454456
}
457+
case CONF_ENUM_LOG_DESTINATION:
458+
{
459+
// Check type
460+
if(!cJSON_IsString(elem))
461+
return "not of type string";
462+
const int log_dest = get_log_destination_val(elem->valuestring);
463+
if(log_dest == -1)
464+
return "invalid option";
465+
// Set item
466+
conf_item->v.log_destination = log_dest;
467+
log_web_debug(DEBUG_CONFIG, "%s = %d", conf_item->k, conf_item->v.log_destination);
468+
break;
469+
}
455470
case CONF_ENUM_PRIVACY_LEVEL:
456471
{
457472
// Check type

src/args.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,6 +1766,16 @@ void suggest_complete(const int argc, char *argv[])
17661766
}
17671767
break;
17681768

1769+
case CONF_ENUM_LOG_DESTINATION:
1770+
// Provide matching suggestions
1771+
for(size_t j = 0; j < LOG_DEST_MAX; j++)
1772+
{
1773+
const char *dest = get_log_destination_str(j);
1774+
if(strStartsWithIgnoreCase(dest, last_word) || strlen(last_word) == 0)
1775+
puts(dest);
1776+
}
1777+
break;
1778+
17691779
case CONF_ENUM_PRIVACY_LEVEL:
17701780
// This enum is in reality a numeric value
17711781
printf("%d\n", (int)conf_item->d.privacy_level);

src/config/cli.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,21 @@ static bool readStringValue(struct conf_item *conf_item, const char *value, stru
311311
}
312312
break;
313313
}
314+
case CONF_ENUM_LOG_DESTINATION:
315+
{
316+
const int dest = get_log_destination_val(value);
317+
if(dest != -1)
318+
conf_item->v.log_destination = dest;
319+
else
320+
{
321+
char *allowed = NULL;
322+
CONFIG_ITEM_ARRAY(conf_item->a, allowed);
323+
log_err("Config setting %s is invalid, allowed options are: %s", conf_item->k, allowed);
324+
free(allowed);
325+
return false;
326+
}
327+
break;
328+
}
314329
case CONF_STRUCT_IN_ADDR:
315330
{
316331
struct in_addr addr4 = { 0 };

src/config/config.c

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ void duplicate_config(struct config *dst, struct config *src)
283283
case CONF_ENUM_WEB_THEME:
284284
case CONF_ENUM_TEMP_UNIT:
285285
case CONF_ENUM_BLOCKING_EDNS_MODE:
286+
case CONF_ENUM_LOG_DESTINATION:
286287
case CONF_STRUCT_IN_ADDR:
287288
case CONF_STRUCT_IN6_ADDR:
288289
case CONF_ALL_DEBUG_BOOL:
@@ -321,6 +322,7 @@ bool compare_config_item(const enum conf_type t, const union conf_value *val1, c
321322
case CONF_ENUM_WEB_THEME:
322323
case CONF_ENUM_TEMP_UNIT:
323324
case CONF_ENUM_BLOCKING_EDNS_MODE:
325+
case CONF_ENUM_LOG_DESTINATION:
324326
case CONF_STRUCT_IN_ADDR:
325327
case CONF_STRUCT_IN6_ADDR:
326328
case CONF_ALL_DEBUG_BOOL:
@@ -377,6 +379,7 @@ void free_config(struct config *conf, const bool terminating)
377379
case CONF_ENUM_WEB_THEME:
378380
case CONF_ENUM_TEMP_UNIT:
379381
case CONF_ENUM_BLOCKING_EDNS_MODE:
382+
case CONF_ENUM_LOG_DESTINATION:
380383
case CONF_STRUCT_IN_ADDR:
381384
case CONF_STRUCT_IN6_ADDR:
382385
case CONF_ALL_DEBUG_BOOL:
@@ -1370,7 +1373,7 @@ void initConfig(struct config *conf)
13701373
conf->files.pcap.c = validate_filepath_empty;
13711374

13721375
// sub-struct files.log
1373-
// conf->files.log.ftl is set in a separate function (getLogFilePath)
1376+
// conf->files.log.ftl and conf->files.log.destination is set in a separate function (getLogFilePath)
13741377

13751378
conf->files.log.dnsmasq.k = "files.log.dnsmasq";
13761379
conf->files.log.dnsmasq.h = "The log file used by the embedded dnsmasq DNS server";
@@ -1978,6 +1981,20 @@ static bool getLogFilePathENV(void)
19781981
return true;
19791982
}
19801983

1984+
static bool getLogDestinationENV(void)
1985+
{
1986+
const char *val = getenv(FTLCONF_PREFIX "files_log_destination");
1987+
if(val == NULL || *val == '\0')
1988+
return false;
1989+
1990+
const int dest = get_log_destination_val(val);
1991+
if(dest == -1)
1992+
return false;
1993+
1994+
config.files.log.destination.v.log_destination = dest;
1995+
return true;
1996+
}
1997+
19811998
bool getLogFilePath(bool try_read)
19821999
{
19832000
// Initialize memory
@@ -1993,9 +2010,38 @@ bool getLogFilePath(bool try_read)
19932010
config.files.log.ftl.c = validate_filepath;
19942011
config.files.log.ftl.f = FLAG_FTL_LOG;
19952012

1996-
// Try sources in priority order: ENV > TOML > legacy
1997-
if(try_read && !getLogFilePathENV() && !getLogFilePathTOML())
1998-
return getLogFilePathLegacy(&config, NULL);
2013+
// Initialize log destination
2014+
config.files.log.destination.k = "files.log.destination";
2015+
config.files.log.destination.h = "Where to write FTL, webserver and dnsmasq log output";
2016+
{
2017+
struct enum_options log_destination[] =
2018+
{
2019+
{ "FILE", "Write logs to the configured log files" },
2020+
{ "JSON", "Write logs as structured JSON to stdout" }
2021+
};
2022+
CONFIG_ADD_ENUM_OPTIONS(config.files.log.destination.a, log_destination);
2023+
}
2024+
config.files.log.destination.t = CONF_ENUM_LOG_DESTINATION;
2025+
config.files.log.destination.f = FLAG_READ_ONLY;
2026+
config.files.log.destination.d.log_destination = LOG_DEST_FILE;
2027+
config.files.log.destination.c = validate_stub;
2028+
2029+
// Read log file path: ENV > TOML > legacy
2030+
// ENV is always checked so it works during early init (try_read=false).
2031+
// When try_read is true, TOML and legacy are also checked. Since ENV
2032+
// runs last, it takes precedence over TOML and legacy if set.
2033+
if(try_read)
2034+
{
2035+
if(!getLogFilePathTOML())
2036+
getLogFilePathLegacy(&config, NULL);
2037+
}
2038+
getLogFilePathENV();
2039+
2040+
// Read log destination: ENV > TOML
2041+
// Same priority logic: ENV runs last and wins if set.
2042+
if(try_read)
2043+
getLogDestinationTOML();
2044+
getLogDestinationENV();
19992045

20002046
return true;
20012047
}
@@ -2047,6 +2093,7 @@ const char * __attribute__ ((const)) get_conf_type_str(const enum conf_type type
20472093
case CONF_ENUM_WEB_THEME:
20482094
case CONF_ENUM_TEMP_UNIT:
20492095
case CONF_ENUM_BLOCKING_EDNS_MODE:
2096+
case CONF_ENUM_LOG_DESTINATION:
20502097
return "enum (string)";
20512098
case CONF_ENUM_PRIVACY_LEVEL:
20522099
return "enum (unsigned integer)";

src/config/config.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ union conf_value {
6767
enum web_theme web_theme; // enum web_theme value
6868
enum temp_unit temp_unit; // enum temp_unit value
6969
enum edns_mode edns_mode; // enum edns_mode value
70+
enum log_destination log_destination; // enum log_destination value
7071
struct in_addr in_addr; // struct in_addr value
7172
struct in6_addr in6_addr; // struct in6_addr value
7273
cJSON *json; // cJSON * value
@@ -90,6 +91,7 @@ enum conf_type {
9091
CONF_ENUM_LISTENING_MODE,
9192
CONF_ENUM_WEB_THEME,
9293
CONF_ENUM_BLOCKING_EDNS_MODE,
94+
CONF_ENUM_LOG_DESTINATION,
9395
CONF_ENUM_TEMP_UNIT,
9496
CONF_STRUCT_IN_ADDR,
9597
CONF_STRUCT_IN6_ADDR,
@@ -311,6 +313,7 @@ struct config {
311313
struct conf_item ftl;
312314
struct conf_item dnsmasq;
313315
struct conf_item webserver;
316+
struct conf_item destination;
314317
} log;
315318
} files;
316319

src/config/env.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,20 @@ bool __attribute__((nonnull(1,2,3))) readEnvValue(struct conf_item *conf_item, s
528528
}
529529
break;
530530
}
531+
case CONF_ENUM_LOG_DESTINATION:
532+
{
533+
const int dest = get_log_destination_val(envvar);
534+
if(dest != -1)
535+
{
536+
conf_item->v.log_destination = dest;
537+
item->valid = true;
538+
}
539+
else
540+
{
541+
invalid_enum_item(envvar, conf_item, item);
542+
}
543+
break;
544+
}
531545
case CONF_ENUM_PRIVACY_LEVEL:
532546
{
533547
int val = 0;

src/config/toml_helper.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,9 @@ void writeTOMLvalue(FILE * fp, const int indent, const enum conf_type t, union c
366366
case CONF_ENUM_BLOCKING_EDNS_MODE:
367367
printTOMLstring(fp, get_edns_mode_str(v->edns_mode), toml);
368368
break;
369+
case CONF_ENUM_LOG_DESTINATION:
370+
printTOMLstring(fp, get_log_destination_str(v->log_destination), toml);
371+
break;
369372
case CONF_STRUCT_IN_ADDR:
370373
{
371374
// Special case: 0.0.0.0 -> return empty string
@@ -658,6 +661,21 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_datum_t to
658661
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid string", conf_item->k);
659662
break;
660663
}
664+
case CONF_ENUM_LOG_DESTINATION:
665+
{
666+
toml_datum_t val = toml_table_find(toml, key);
667+
if(val.type == TOML_STRING)
668+
{
669+
const int dest = get_log_destination_val(val.u.s);
670+
if(dest != -1)
671+
conf_item->v.log_destination = dest;
672+
else
673+
log_warn("Config setting %s is invalid, allowed options are: %s", conf_item->k, conf_item->h);
674+
}
675+
else
676+
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid string", conf_item->k);
677+
break;
678+
}
661679
case CONF_ENUM_PRIVACY_LEVEL:
662680
{
663681
const toml_datum_t val = toml_table_find(toml, key);

src/config/toml_reader.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "toml_reader.h"
1313
#include "config/setupVars.h"
1414
#include "log.h"
15+
#include "datastructure.h"
1516
// getprio(), setprio()
1617
#include <sys/resource.h>
1718
// argv_dnsmasq
@@ -348,6 +349,49 @@ bool getLogFilePathTOML(void)
348349
return true;
349350
}
350351

352+
bool getLogDestinationTOML(void)
353+
{
354+
log_debug(DEBUG_CONFIG, "Reading TOML config file: log destination");
355+
356+
toml_result_t conf = { 0 };
357+
358+
if(!parseTOML(&conf, 0))
359+
return false;
360+
361+
toml_datum_t files = toml_table_find(conf.toptab, "files");
362+
if(files.type != TOML_TABLE)
363+
{
364+
log_debug(DEBUG_CONFIG, "files DOES NOT EXIST or is not a table");
365+
toml_free(conf);
366+
return false;
367+
}
368+
369+
toml_datum_t log = toml_table_find(files, "log");
370+
if(log.type != TOML_TABLE)
371+
{
372+
log_debug(DEBUG_CONFIG, "files.log DOES NOT EXIST or is not a table");
373+
toml_free(conf);
374+
return false;
375+
}
376+
377+
toml_datum_t destination = toml_table_find(log, "destination");
378+
if(destination.type != TOML_STRING)
379+
{
380+
log_debug(DEBUG_CONFIG, "files.log.destination DOES NOT EXIST or is not an enum");
381+
toml_free(conf);
382+
return false;
383+
}
384+
385+
const int dest = get_log_destination_val(destination.u.s);
386+
if(dest != -1)
387+
config.files.log.destination.v.log_destination = dest;
388+
else
389+
log_warn("Config setting %s is invalid, allowed options are: %s", "files.log.destination", "FILE, JSON");
390+
391+
toml_free(conf);
392+
return true;
393+
}
394+
351395
static void reportDebugFlags(void)
352396
{
353397
// Print debug settings

src/config/toml_reader.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ bool readFTLtoml(struct config *oldconf, struct config *newconf,
1717
toml_datum_t toml, const bool verbose, bool *restart,
1818
const unsigned int version, const bool teleporter);
1919
bool getLogFilePathTOML(void);
20+
bool getLogDestinationTOML(void);
2021

2122
#endif //TOML_READER_H

src/datastructure.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,6 +1523,31 @@ int __attribute__ ((pure)) get_listeningMode_val(const char *listeningMode)
15231523
return -1;
15241524
}
15251525

1526+
const char * __attribute__ ((const)) get_log_destination_str(const enum log_destination dest)
1527+
{
1528+
switch(dest)
1529+
{
1530+
case LOG_DEST_FILE:
1531+
return "FILE";
1532+
case LOG_DEST_JSON:
1533+
return "JSON";
1534+
case LOG_DEST_MAX:
1535+
default:
1536+
return NULL;
1537+
}
1538+
}
1539+
1540+
int __attribute__ ((pure)) get_log_destination_val(const char *dest)
1541+
{
1542+
if(strcasecmp(dest, "FILE") == 0)
1543+
return LOG_DEST_FILE;
1544+
else if(strcasecmp(dest, "JSON") == 0)
1545+
return LOG_DEST_JSON;
1546+
1547+
// Invalid value
1548+
return -1;
1549+
}
1550+
15261551
const char * __attribute__ ((const)) get_temp_unit_str(const enum temp_unit temp_unit)
15271552
{
15281553
switch(temp_unit)

0 commit comments

Comments
 (0)