MINOR: log: add log-profile parsing logic

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)
This commit is contained in:
Aurelien DARRAGON 2024-05-22 17:09:48 +02:00
parent 33f3bec7ee
commit 15e9c7da6b
4 changed files with 365 additions and 7 deletions

View 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 */
/*

View 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);

View 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 */
};

332
src/log.c
View File

@ -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: