]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: log: add log-profile parsing logic
authorAurelien DARRAGON <adarragon@haproxy.com>
Wed, 22 May 2024 15:09:48 +0000 (17:09 +0200)
committerAurelien DARRAGON <adarragon@haproxy.com>
Thu, 13 Jun 2024 13:43:09 +0000 (15:43 +0200)
This patch implements prerequisite log-profile struct and parser logic.
It has no effect during runtime for now.

Logformat expressions provided in log-profile "steps" are postchecked
during postparsing for each proxy "log" directive that makes use of a
given profile. (this allows to ensure that the logformat expressions
used in the profile are compatible with proxy using them)

include/haproxy/log-t.h
include/haproxy/log.h
src/cfgparse-global.c
src/log.c

index 3e5b6900a55ed3958154c7dff85cc5712509cd08..11161d24123f87d39ed468a22502203d0d08a206 100644 (file)
@@ -233,6 +233,7 @@ struct log_target {
 
 enum logger_flags {
        LOGGER_FL_NONE     = 0x00,
+       LOGGER_FL_RESOLVED = 0x01,
 };
 
 struct logger {
@@ -247,6 +248,10 @@ struct logger {
        int minlvl;
        int maxlen;
        struct logger *ref;
+       union {
+               struct log_profile *prof; /* postparsing */
+               char *prof_str;           /* preparsing */
+       };
        struct {
                 char *file;                     /* file where the logger appears */
                 int line;                       /* line where the logger appears */
@@ -267,6 +272,28 @@ enum log_orig {
        LOG_ORIG_TXN_CLOSE,          /* during stream termination */
 };
 
+struct log_profile_step {
+       struct lf_expr logformat;
+       struct lf_expr logformat_sd;
+};
+
+struct log_profile {
+       struct list list;
+       struct {
+               char *file;
+               int line;
+       } conf;
+       char *id;
+       struct buffer log_tag;          // override log-tag
+       struct log_profile_step *accept;
+       struct log_profile_step *request;
+       struct log_profile_step *connect;
+       struct log_profile_step *response;
+       struct log_profile_step *close;
+       struct log_profile_step *error; // override error-log-format
+       struct log_profile_step *any;   // override log-format
+};
+
 #endif /* _HAPROXY_LOG_T_H */
 
 /*
index c08a11c319023d0f68f802813d4f399da85569ac..10f50a523b2f2aca94dfe86ab8ddea497aeb7df9 100644 (file)
@@ -134,6 +134,7 @@ int postresolve_logger_list(struct proxy *px, struct list *loggers, const char *
 struct logger *dup_logger(struct logger *def);
 void free_logger(struct logger *logger);
 void deinit_log_target(struct log_target *target);
+struct log_profile *log_profile_find_by_name(const char *name);
 
 /* Parse "log" keyword and update the linked list. */
 int parse_logger(char **args, struct list *loggers, int do_del, const char *file, int linenum, char **err);
index 452c0e54536bb8894b77dd9bc0a2d19282197925..0a081a339186cd8de8164b645eaa97e67289f850 100644 (file)
@@ -46,12 +46,12 @@ static const char *common_kw_list[] = {
        "ssl-server-verify", "maxconnrate", "maxsessrate", "maxsslrate",
        "maxcomprate", "maxpipes", "maxzlibmem", "maxcompcpuusage", "ulimit-n",
        "chroot", "description", "node", "pidfile", "unix-bind", "log",
-       "log-send-hostname", "server-state-base", "server-state-file",
-       "log-tag", "spread-checks", "max-spread-checks", "cpu-map", "setenv",
-       "presetenv", "unsetenv", "resetenv", "strict-limits", "localpeer",
-       "numa-cpu-mapping", "defaults", "listen", "frontend", "backend",
-       "peers", "resolvers", "cluster-secret", "no-quic", "limited-quic",
-       "stats-file",
+       "log-profile", "log-send-hostname", "log-tag", "server-state-base",
+       "server-state-file", "spread-checks", "max-spread-checks", "cpu-map",
+       "setenv", "presetenv", "unsetenv", "resetenv", "strict-limits",
+       "localpeer", "numa-cpu-mapping", "defaults", "listen", "frontend",
+       "backend", "peers", "resolvers", "cluster-secret", "no-quic",
+       "limited-quic", "stats-file",
        NULL /* must be last */
 };
 
index cee65110244e7f3d8328989caad8849b21316a10..eb6a4b4258a8696a08309b2ad02a04a8fdc11600 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -361,6 +361,8 @@ struct logformat_node_args node_args_list[] = {
        {  0,  0 }
 };
 
+static struct list log_profile_list = LIST_HEAD_INIT(log_profile_list);
+
 /*
  * callback used to configure addr source retrieval
  */
@@ -1403,6 +1405,9 @@ static int postcheck_log_backend(struct proxy *be)
        return err_code;
 }
 
+/* forward declaration */
+static int log_profile_postcheck(struct proxy *px, struct log_profile *prof, char **err);
+
 /* resolves a single logger entry (it is expected to be called
  * at postparsing stage)
  *
@@ -1419,6 +1424,7 @@ static int resolve_logger(struct proxy *px, struct logger *logger, char **msg)
        struct log_target *target = &logger->target;
        int err_code = ERR_NONE;
 
+       /* resolve logger target */
        if (target->type == LOG_TARGET_BUFFER)
                err_code = sink_resolve_logger_buffer(logger, msg);
        else if (target->type == LOG_TARGET_BACKEND) {
@@ -1440,17 +1446,46 @@ static int resolve_logger(struct proxy *px, struct logger *logger, char **msg)
 
        target->flags |= LOG_TARGET_FL_RESOLVED;
 
+       if (err_code & ERR_CODE)
+               goto end;
+
+       /* postcheck logger profile */
+       if (logger->prof_str) {
+               struct log_profile *prof;
+
+               prof = log_profile_find_by_name(logger->prof_str);
+               if (!prof) {
+                       memprintf(msg, "unknown log-profile '%s'", logger->prof_str);
+                       ha_free(&logger->prof_str);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto end;
+               }
+               ha_free(&logger->prof_str);
+               logger->prof = prof;
+
+               if (!log_profile_postcheck(px, logger->prof, msg)) {
+                       memprintf(msg, "uses incompatible log-profile '%s': %s", logger->prof->id, *msg);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+               }
+       }
+
+ end:
+       logger->flags |= LOGGER_FL_RESOLVED;
        return err_code;
 }
 
 /* tries to duplicate <def> logger
+ * (only possible before the logger is resolved)
  *
  * Returns the newly allocated and duplicated logger or NULL
  * in case of error.
  */
 struct logger *dup_logger(struct logger *def)
 {
-       struct logger *cpy = malloc(sizeof(*cpy));
+       struct logger *cpy;
+
+       BUG_ON(def->flags & LOGGER_FL_RESOLVED);
+       cpy = malloc(sizeof(*cpy));
 
        /* copy everything that can be easily copied */
        memcpy(cpy, def, sizeof(*cpy));
@@ -1475,6 +1510,11 @@ struct logger *dup_logger(struct logger *def)
                memcpy(cpy->lb.smp_rgs, def->lb.smp_rgs,
                       sizeof(*cpy->lb.smp_rgs) * def->lb.smp_rgs_sz);
        }
+       if (def->prof_str) {
+               cpy->prof_str = strdup(def->prof_str);
+               if (!cpy->prof_str)
+                       goto error;
+       }
 
        /* inherit from original reference if set */
        cpy->ref = (def->ref) ? def->ref : def;
@@ -1499,6 +1539,8 @@ void free_logger(struct logger *logger)
        ha_free(&logger->conf.file);
        deinit_log_target(&logger->target);
        free(logger->lb.smp_rgs);
+       if (!(logger->flags & LOGGER_FL_RESOLVED))
+               ha_free(&logger->prof_str);
        free(logger);
 }
 
@@ -1764,6 +1806,18 @@ int parse_logger(char **args, struct list *loggers, int do_del, const char *file
                cur_arg += 2;
        }
 
+       if (strcmp(args[cur_arg], "profile") == 0) {
+               char *prof_str;
+
+               prof_str = args[cur_arg+1];
+               if (!prof_str) {
+                       memprintf(err, "expected log-profile name");
+                       goto error;
+               }
+               logger->prof_str = strdup(prof_str);
+               cur_arg += 2;
+       }
+
        /* parse the facility */
        logger->facility = get_log_facility(args[cur_arg]);
        if (logger->facility < 0) {
@@ -5884,6 +5938,280 @@ out:
        return err_code;
 }
 
+static inline void log_profile_step_init(struct log_profile_step *lprof_step)
+{
+       lf_expr_init(&lprof_step->logformat);
+       lf_expr_init(&lprof_step->logformat_sd);
+}
+
+static inline void log_profile_step_free(struct log_profile_step *lprof_step)
+{
+       if (!lprof_step)
+               return;
+
+       lf_expr_deinit(&lprof_step->logformat);
+       lf_expr_deinit(&lprof_step->logformat_sd);
+       free(lprof_step);
+}
+
+/* postcheck a single log profile step for a given <px> (it is expected to be
+ * called at postparsing stage)
+ *
+ * Returns 1 on success and 0 on error, <msg> will be set on error.
+ */
+static inline int log_profile_step_postcheck(struct proxy *px, const char *step_name,
+                                             struct log_profile_step *step,
+                                             char **err)
+{
+       if (!step)
+               return 1; // nothing to do
+
+       if (!lf_expr_isempty(&step->logformat) &&
+           !lf_expr_postcheck(&step->logformat, px, err)) {
+               memprintf(err, "'on %s format' in file '%s' at line %d: %s",
+                         step_name,
+                         step->logformat_sd.conf.file,
+                         step->logformat_sd.conf.line,
+                         *err);
+               return 0;
+       }
+       if (!lf_expr_isempty(&step->logformat_sd) &&
+           !lf_expr_postcheck(&step->logformat_sd, px, err)) {
+               memprintf(err, "'on %s sd' in file '%s' at line %d: %s",
+                         step_name,
+                         step->logformat_sd.conf.file,
+                         step->logformat_sd.conf.line,
+                         *err);
+               return 0;
+       }
+
+       return 1;
+}
+
+/* postcheck a log profile struct for a given <px> (it is expected to be called
+ * at postparsing stage)
+ *
+ * Returns 1 on success and 0 on error, <msg> will be set on error.
+ */
+static int log_profile_postcheck(struct proxy *px, struct log_profile *prof, char **err)
+{
+       /* log profile steps are only relevant under proxy
+        * context
+        */
+       if (!px)
+               return 1; /* nothing to do */
+
+       /* postcheck lf_expr for log profile steps */
+       if (!log_profile_step_postcheck(px, "accept", prof->accept, err) ||
+           !log_profile_step_postcheck(px, "request", prof->request, err) ||
+           !log_profile_step_postcheck(px, "connect", prof->connect, err) ||
+           !log_profile_step_postcheck(px, "response", prof->response, err) ||
+           !log_profile_step_postcheck(px, "close", prof->close, err) ||
+           !log_profile_step_postcheck(px, "error", prof->error, err) ||
+           !log_profile_step_postcheck(px, "any", prof->any, err))
+               return 0;
+
+       return 1;
+}
+
+static void log_profile_free(struct log_profile *prof)
+{
+       ha_free(&prof->id);
+       ha_free(&prof->conf.file);
+       chunk_destroy(&prof->log_tag);
+
+       log_profile_step_free(prof->accept);
+       log_profile_step_free(prof->request);
+       log_profile_step_free(prof->connect);
+       log_profile_step_free(prof->response);
+       log_profile_step_free(prof->close);
+       log_profile_step_free(prof->error);
+       log_profile_step_free(prof->any);
+
+       ha_free(&prof);
+}
+
+/* Deinitialize all known log profiles */
+static void deinit_log_profiles()
+{
+       struct log_profile *prof, *back;
+
+       list_for_each_entry_safe(prof, back, &log_profile_list, list) {
+               LIST_DEL_INIT(&prof->list);
+               log_profile_free(prof);
+       }
+}
+
+struct log_profile *log_profile_find_by_name(const char *name)
+{
+       struct log_profile *current;
+
+       list_for_each_entry(current, &log_profile_list, list) {
+               if (strcmp(current->id, name) == 0)
+                       return current;
+       }
+       return NULL;
+}
+
+/*
+ * Parse "log-profile" section and register the corresponding profile
+ * with its name
+ *
+ * The function returns 0 in success case, otherwise, it returns error
+ * flags.
+ */
+int cfg_parse_log_profile(const char *file, int linenum, char **args, int kwm)
+{
+       int err_code = ERR_NONE;
+       static struct log_profile *prof = NULL;
+       char *errmsg = NULL;
+       const char *err = NULL;
+
+       if (strcmp(args[0], "log-profile") == 0) {
+               if (!*args[1]) {
+                       ha_alert("parsing [%s:%d] : missing name for log-profile section.\n", file, linenum);
+                       err_code |= ERR_ALERT | ERR_ABORT;
+                       goto out;
+               }
+
+               if (alertif_too_many_args(1, file, linenum, args, &err_code))
+                       goto out;
+
+               err = invalid_char(args[1]);
+               if (err) {
+                       ha_alert("parsing [%s:%d] : character '%c' is not permitted in '%s' name '%s'.\n",
+                                file, linenum, *err, args[0], args[1]);
+                       err_code |= ERR_ALERT | ERR_ABORT;
+                       goto out;
+               }
+
+               prof = log_profile_find_by_name(args[1]);
+               if (prof) {
+                       ha_alert("Parsing [%s:%d]: log-profile section '%s' has the same name as another log-profile section declared at %s:%d.\n",
+                                file, linenum, args[1], prof->conf.file, prof->conf.line);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+               prof = calloc(1, sizeof(*prof));
+               if (prof == NULL || !(prof->id = strdup(args[1]))) {
+                       ha_alert("Parsing [%s:%d]: cannot allocate memory for log-profile section '%s'.\n",
+                                file, linenum, args[1]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+               prof->conf.file = strdup(file);
+               prof->conf.line = linenum;
+
+               /* add to list */
+               LIST_APPEND(&log_profile_list, &prof->list);
+       }
+       else if (strcmp(args[0], "log-tag") == 0) {  /* override log-tag */
+               if (*(args[1]) == 0) {
+                       ha_alert("parsing [%s:%d] : '%s' expects a tag for use in syslog.\n", file, linenum, args[0]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+               chunk_destroy(&prof->log_tag);
+               chunk_initlen(&prof->log_tag, strdup(args[1]), strlen(args[1]), strlen(args[1]));
+               if (b_orig(&prof->log_tag) == NULL) {
+                       chunk_destroy(&prof->log_tag);
+                       ha_alert("parsing [%s:%d]: cannot allocate memory for '%s'.\n", file, linenum, args[0]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+       }
+       else if (strcmp(args[0], "on") == 0) { /* log profile step */
+               struct log_profile_step **target_step;
+               struct lf_expr *target_lf;
+               int cur_arg;
+
+               /* get targeted log-profile step */
+               if (strcmp(args[1], "accept") == 0)
+                       target_step = &prof->accept;
+               else if (strcmp(args[1], "request") == 0)
+                       target_step = &prof->request;
+               else if (strcmp(args[1], "connect") == 0)
+                       target_step = &prof->connect;
+               else if (strcmp(args[1], "response") == 0)
+                       target_step = &prof->response;
+               else if (strcmp(args[1], "close") == 0)
+                       target_step = &prof->close;
+               else if (strcmp(args[1], "error") == 0)
+                       target_step = &prof->error;
+               else if (strcmp(args[1], "any") == 0)
+                       target_step = &prof->any;
+               else {
+                       ha_alert("parsing [%s:%d] : '%s' expects a log step.\n"
+                                "expected values are: 'accept', 'request', 'connect', 'response', 'close', 'error' or 'any'\n",
+                                file, linenum, args[0]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+
+               if (*target_step == NULL) {
+                       /* first time */
+                       *target_step = malloc(sizeof(**target_step));
+                       if (*target_step == NULL) {
+                               ha_alert("parsing [%s:%d]: cannot allocate memory for '%s %s'.\n", file, linenum, args[0], args[1]);
+                               err_code |= ERR_ALERT | ERR_FATAL;
+                               goto out;
+                       }
+                       log_profile_step_init(*target_step);
+               }
+
+               cur_arg = 2;
+
+               while (*(args[cur_arg]) != 0) {
+                       /* regular format or SD (structured-data) one? */
+                       if (strcmp(args[cur_arg], "format") == 0)
+                               target_lf = &(*target_step)->logformat;
+                       else if (strcmp(args[cur_arg], "sd") == 0)
+                               target_lf = &(*target_step)->logformat_sd;
+                       else
+                               break;
+
+                       /* parse and assign logformat expression */
+                       lf_expr_deinit(target_lf); /* if already configured */
+
+                       if (*(args[cur_arg + 1]) == 0) {
+                               ha_alert("parsing [%s:%d] : '%s %s %s' expects a logformat string.\n",
+                                        file, linenum, args[0], args[1], args[cur_arg]);
+                               err_code |= ERR_ALERT | ERR_FATAL;
+                               goto out;
+                       }
+
+                       target_lf->str = strdup(args[cur_arg + 1]);
+                       target_lf->conf.file = strdup(file);
+                       target_lf->conf.line = linenum;
+
+                       if (!lf_expr_compile(target_lf, NULL,
+                                            LOG_OPT_MANDATORY|LOG_OPT_MERGE_SPACES,
+                                            SMP_VAL_FE_LOG_END, &errmsg)) {
+                               ha_alert("Parsing [%s:%d]: failed to parse logformat: %s.\n",
+                                        file, linenum, errmsg);
+                               err_code |= ERR_ALERT | ERR_FATAL;
+                               goto out;
+                       }
+
+                       cur_arg += 2;
+               }
+               if (cur_arg == 2 || *(args[cur_arg]) != 0) {
+                       ha_alert("parsing [%s:%d] : '%s %s' expects 'format' and/or 'sd'.\n",
+                                file, linenum, args[0], args[1]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+       }
+       else {
+               ha_alert("parsing [%s:%d] : unknown keyword '%s' in log-profile section.\n", file, linenum, args[0]);
+               err_code |= ERR_ALERT | ERR_ABORT;
+               goto out;
+       }
+out:
+       ha_free(&errmsg);
+       return err_code;
+}
+
 /* function: post-resolve a single list of loggers
  *
  * Returns err_code which defaults to ERR_NONE and can be set to a combination
@@ -5945,6 +6273,7 @@ static int postresolve_loggers()
 
 /* config parsers for this section */
 REGISTER_CONFIG_SECTION("log-forward", cfg_parse_log_forward, NULL);
+REGISTER_CONFIG_SECTION("log-profile", cfg_parse_log_profile, NULL);
 REGISTER_POST_CHECK(postresolve_loggers);
 REGISTER_POST_PROXY_CHECK(postcheck_log_backend);
 REGISTER_POST_PROXY_CHECK(postcheck_logformat_proxy);
@@ -5953,6 +6282,7 @@ REGISTER_PER_THREAD_ALLOC(init_log_buffers);
 REGISTER_PER_THREAD_FREE(deinit_log_buffers);
 
 REGISTER_POST_DEINIT(deinit_log_forward);
+REGISTER_POST_DEINIT(deinit_log_profiles);
 
 /*
  * Local variables: