diff options
Diffstat (limited to 'collectors/log2journal/log2journal-logfmt.c')
-rw-r--r-- | collectors/log2journal/log2journal-logfmt.c | 226 |
1 files changed, 226 insertions, 0 deletions
diff --git a/collectors/log2journal/log2journal-logfmt.c b/collectors/log2journal/log2journal-logfmt.c new file mode 100644 index 00000000..5966cce9 --- /dev/null +++ b/collectors/log2journal/log2journal-logfmt.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "log2journal.h" + +#define LOGFMT_ERROR_LINE_MAX 1024 +#define LOGFMT_KEY_MAX 1024 + +struct logfmt_state { + LOG_JOB *jb; + + const char *line; + uint32_t pos; + uint32_t key_start; + + char key[LOGFMT_KEY_MAX]; + char msg[LOGFMT_ERROR_LINE_MAX]; +}; + +#define logfmt_current_pos(lfs) &(lfs)->line[(lfs)->pos] +#define logfmt_consume_char(lfs) ++(lfs)->pos + +static inline void logfmt_process_key_value(LOGFMT_STATE *lfs, const char *value, size_t len) { + log_job_send_extracted_key_value(lfs->jb, lfs->key, value, len); +} + +static inline void logfmt_skip_spaces(LOGFMT_STATE *lfs) { + const char *s = logfmt_current_pos(lfs); + const char *start = s; + + while(isspace(*s)) s++; + + lfs->pos += s - start; +} + +static inline void copy_newline(LOGFMT_STATE *lfs __maybe_unused, char **d, size_t *remaining) { + if(*remaining > 3) { + *(*d)++ = '\\'; + *(*d)++ = 'n'; + (*remaining) -= 2; + } +} + +static inline void copy_tab(LOGFMT_STATE *lfs __maybe_unused, char **d, size_t *remaining) { + if(*remaining > 3) { + *(*d)++ = '\\'; + *(*d)++ = 't'; + (*remaining) -= 2; + } +} + +static inline bool logftm_parse_value(LOGFMT_STATE *lfs) { + static __thread char value[JOURNAL_MAX_VALUE_LEN]; + + char quote = '\0'; + const char *s = logfmt_current_pos(lfs); + if(*s == '\"' || *s == '\'') { + quote = *s; + logfmt_consume_char(lfs); + } + + value[0] = '\0'; + char *d = value; + s = logfmt_current_pos(lfs); + size_t remaining = sizeof(value); + + char end_char = (char)(quote == '\0' ? ' ' : quote); + while (*s && *s != end_char) { + char c; + + if (*s == '\\') { + s++; + + switch (*s) { + case 'n': + copy_newline(lfs, &d, &remaining); + s++; + continue; + + case 't': + copy_tab(lfs, &d, &remaining); + s++; + continue; + + case 'f': + case 'b': + case 'r': + c = ' '; + s++; + break; + + default: + c = *s++; + break; + } + } + else + c = *s++; + + if(remaining < 2) { + snprintf(lfs->msg, sizeof(lfs->msg), + "LOGFMT PARSER: truncated string value at pos %zu", lfs->pos); + return false; + } + else { + *d++ = c; + remaining--; + } + } + *d = '\0'; + lfs->pos += s - logfmt_current_pos(lfs); + + s = logfmt_current_pos(lfs); + + if(quote != '\0') { + if (*s != quote) { + snprintf(lfs->msg, sizeof(lfs->msg), + "LOGFMT PARSER: missing quote at pos %zu: '%s'", + lfs->pos, s); + return false; + } + else + logfmt_consume_char(lfs); + } + + if(d > value) + logfmt_process_key_value(lfs, value, d - value); + + return true; +} + +static inline bool logfmt_parse_key(LOGFMT_STATE *lfs) { + logfmt_skip_spaces(lfs); + + char *d = &lfs->key[lfs->key_start]; + + size_t remaining = sizeof(lfs->key) - (d - lfs->key); + + const char *s = logfmt_current_pos(lfs); + char last_c = '\0'; + while(*s && *s != '=') { + char c; + + if (*s == '\\') + s++; + + c = journal_key_characters_map[(unsigned char)*s++]; + + if(c == '_' && last_c == '_') + continue; + else { + if(remaining < 2) { + snprintf(lfs->msg, sizeof(lfs->msg), + "LOGFMT PARSER: key buffer full - keys are too long, at pos %zu", lfs->pos); + return false; + } + *d++ = c; + remaining--; + } + + last_c = c; + } + *d = '\0'; + lfs->pos += s - logfmt_current_pos(lfs); + + s = logfmt_current_pos(lfs); + if(*s != '=') { + snprintf(lfs->msg, sizeof(lfs->msg), + "LOGFMT PARSER: key is missing the equal sign, at pos %zu", lfs->pos); + return false; + } + + logfmt_consume_char(lfs); + + return true; +} + +LOGFMT_STATE *logfmt_parser_create(LOG_JOB *jb) { + LOGFMT_STATE *lfs = mallocz(sizeof(LOGFMT_STATE)); + memset(lfs, 0, sizeof(LOGFMT_STATE)); + lfs->jb = jb; + + if(jb->prefix) + lfs->key_start = copy_to_buffer(lfs->key, sizeof(lfs->key), lfs->jb->prefix, strlen(lfs->jb->prefix)); + + return lfs; +} + +void logfmt_parser_destroy(LOGFMT_STATE *lfs) { + if(lfs) + freez(lfs); +} + +const char *logfmt_parser_error(LOGFMT_STATE *lfs) { + return lfs->msg; +} + +bool logfmt_parse_document(LOGFMT_STATE *lfs, const char *txt) { + lfs->line = txt; + lfs->pos = 0; + lfs->msg[0] = '\0'; + + const char *s; + do { + if(!logfmt_parse_key(lfs)) + return false; + + if(!logftm_parse_value(lfs)) + return false; + + logfmt_skip_spaces(lfs); + + s = logfmt_current_pos(lfs); + } while(*s); + + return true; +} + + +void logfmt_test(void) { + LOG_JOB jb = { .prefix = "NIGNX_" }; + LOGFMT_STATE *logfmt = logfmt_parser_create(&jb); + + logfmt_parse_document(logfmt, "x=1 y=2 z=\"3 \\ 4\" 5 "); + + logfmt_parser_destroy(logfmt); +} |