haproxy/addons/otel/src/parser.c
Miroslav Zagorac 6f177cd01e MINOR: otel: added log-record signal support
Added "log-record" as the third OpenTelemetry signal alongside traces
(span) and metrics (instrument).  This includes the
flt_otel_conf_log_record structure definition, parser keyword defines,
the otel-scope section parser with optional "id", "event", "span", and
"attr" keywords followed by sample fetch expressions or a log-format
string, init/free lifecycle, scope list wiring, log-format evaluation
in flt_otel_scope_run_instrument_record(), a test configuration example,
log-record span reference validation in flt_otel_check(), and logger
handle creation, startup, and teardown in the filter lifecycle.
2026-04-13 09:23:26 +02:00

1826 lines
66 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "../include/include.h"
#ifdef OTELC_DBG_MEM
static struct otelc_dbg_mem_data dbg_mem_data[1000000];
static struct otelc_dbg_mem dbg_mem;
#endif
static struct flt_otel_conf *flt_otel_current_config = NULL;
static struct flt_otel_conf_instr *flt_otel_current_instr = NULL;
static struct flt_otel_conf_group *flt_otel_current_group = NULL;
static struct flt_otel_conf_scope *flt_otel_current_scope = NULL;
static struct flt_otel_conf_span *flt_otel_current_span = NULL;
/***
* NAME
* flt_otel_parse_strdup - string duplication with error handling
*
* SYNOPSIS
* static int flt_otel_parse_strdup(char **dst, size_t *dst_len, const char *src, char **err, const char *err_msg)
*
* ARGUMENTS
* dst - pointer to the destination string pointer
* dst_len - optional pointer to store the duplicated string length
* src - source string to duplicate
* err - indirect pointer to error message string
* err_msg - context label used in error messages
*
* DESCRIPTION
* Duplicates the string <src> into <*dst> with error handling. Optionally
* stores the string length in <dst_len>. On failure, an error message is
* formatted using <err_msg> as context.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse_strdup(char **dst, size_t *dst_len, const char *src, char **err, const char *err_msg)
{
int retval = ERR_NONE;
OTELC_FUNC("%p:%p, %p, %p, %p:%p, \"%s\"", OTELC_DPTR_ARGS(dst), dst_len, src, OTELC_DPTR_ARGS(err), OTELC_STR_ARG(err_msg));
/* dst_len is not set if the string has not been copied. */
*dst = OTELC_STRDUP(src);
if (*dst == NULL)
FLT_OTEL_PARSE_ERR(err, "'%s' : out of memory", err_msg);
else if (dst_len != NULL)
*dst_len = strlen(*dst);
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse_keyword - keyword argument parser
*
* SYNOPSIS
* static int flt_otel_parse_keyword(char **ptr, char **args, int cur_arg, int pos, char **err, const char *err_msg)
*
* ARGUMENTS
* ptr - pointer to the destination string pointer
* args - configuration line arguments array
* cur_arg - current argument index for error reporting
* pos - position of the keyword in <args>
* err - indirect pointer to error message string
* err_msg - context label used in error messages
*
* DESCRIPTION
* Parses a single keyword argument from the configuration line. Checks
* that the keyword has not already been set and that a value is present
* at position <pos> + 1 in <args>. The value is duplicated via
* flt_otel_parse_strdup() into <*ptr>.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse_keyword(char **ptr, char **args, int cur_arg, int pos, char **err, const char *err_msg)
{
int retval = ERR_NONE;
OTELC_FUNC("%p:%p, %p, %d, %d, %p:%p, \"%s\"", OTELC_DPTR_ARGS(ptr), args, cur_arg, pos, OTELC_DPTR_ARGS(err), OTELC_STR_ARG(err_msg));
/* Reject duplicate keyword assignments. */
if (*ptr != NULL) {
if (cur_arg == pos)
FLT_OTEL_PARSE_ERR(err, FLT_OTEL_FMT_TYPE "%s already set", err_msg);
else
FLT_OTEL_PARSE_ERR(err, "'%s' : %s already set", args[cur_arg], err_msg);
}
else if (!FLT_OTEL_ARG_ISVALID(pos + 1)) {
if (cur_arg == pos)
FLT_OTEL_PARSE_ERR(err, FLT_OTEL_FMT_TYPE "no %s set", err_msg);
else
FLT_OTEL_PARSE_ERR(err, "'%s' : no %s set", args[cur_arg], err_msg);
}
else {
retval = flt_otel_parse_strdup(ptr, NULL, args[pos + 1], err, args[cur_arg]);
}
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse_invalid_char - name character validation
*
* SYNOPSIS
* static const char *flt_otel_parse_invalid_char(const char *name, int type)
*
* ARGUMENTS
* name - string to validate
* type - validation type selector
*
* DESCRIPTION
* Validates characters in a <name> string according to the specified <type>.
* Uses HAProxy's invalid_char() for identifiers, invalid_domainchar() for
* domains, invalid_prefix_char() for context prefixes, and a custom
* alphanumeric check for variables.
*
* RETURN VALUE
* Returns a pointer to the first invalid character in <name>,
* or NULL if all characters are valid.
*/
static const char *flt_otel_parse_invalid_char(const char *name, int type)
{
const char *retptr = NULL;
OTELC_FUNC("\"%s\", %d", OTELC_STR_ARG(name), type);
if (!OTELC_STR_IS_VALID(name))
OTELC_RETURN_EX(retptr, const char *, "%p");
/* Dispatch to the appropriate character validation function. */
if (type == FLT_OTEL_PARSE_INVALID_CHAR) {
retptr = invalid_char(name);
}
else if (type == FLT_OTEL_PARSE_INVALID_DOM) {
retptr = invalid_domainchar(name);
}
else if (type == FLT_OTEL_PARSE_INVALID_CTX) {
retptr = invalid_prefix_char(name);
}
else if (type == FLT_OTEL_PARSE_INVALID_VAR) {
retptr = name;
/*
* Allowed characters are letters, numbers and '_', the first
* character in the string must not be a number.
*/
if (!isdigit(*retptr))
for (++retptr; (*retptr == '_') || isalnum(*retptr); retptr++);
if (*retptr == '\0')
retptr = NULL;
}
OTELC_RETURN_EX(retptr, const char *, "%p");
}
/***
* NAME
* flt_otel_parse_cfg_check - configuration keyword validation
*
* SYNOPSIS
* static int flt_otel_parse_cfg_check(const char *file, int line, char **args, const void *id, const struct flt_otel_parse_data *parse_data, size_t parse_data_size, const struct flt_otel_parse_data **pdata, char **err)
*
* ARGUMENTS
* file - configuration file path
* line - configuration file line number
* args - configuration line arguments array
* id - parent section identifier
* parse_data - keyword definition table
* parse_data_size - number of entries in <parse_data>
* pdata - output pointer to the matched keyword entry
* err - indirect pointer to error message string
*
* DESCRIPTION
* Common validation for configuration keywords. Looks up <args[0]> in the
* <parse_data> table, checks argument count bounds, validates the first
* argument's characters according to the keyword's check_name type, and
* verifies that the parent section ID is set when required.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse_cfg_check(const char *file, int line, char **args, const void *id, const struct flt_otel_parse_data *parse_data, size_t parse_data_size, const struct flt_otel_parse_data **pdata, char **err)
{
int i, argc, retval = ERR_NONE;
OTELC_FUNC("\"%s\", %d, %p, %p, %p, %zu, %p:%p, %p:%p", OTELC_STR_ARG(file), line, args, id, parse_data, parse_data_size, OTELC_DPTR_ARGS(pdata), OTELC_DPTR_ARGS(err));
FLT_OTEL_ARGS_DUMP();
*pdata = NULL;
/* First check here if args[0] is the correct keyword. */
for (i = 0; (*pdata == NULL) && (i < parse_data_size); i++)
if (FLT_OTEL_PARSE_KEYWORD(0, parse_data[i].name))
*pdata = parse_data + i;
if (*pdata == NULL)
FLT_OTEL_PARSE_ERR(err, "'%s' : unknown keyword", args[0]);
else
argc = flt_otel_args_count((const char **)args);
if ((retval & ERR_CODE) || (id == NULL))
/* Do nothing. */;
else if ((id != flt_otel_current_instr) && (flt_otel_current_config->instr == NULL))
FLT_OTEL_PARSE_ERR(err, "instrumentation not defined");
/*
* Checking that fewer arguments are specified in the configuration
* line than is required.
*/
if (!(retval & ERR_CODE))
if (argc < (*pdata)->args_min)
FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[0], (*pdata)->name, (*pdata)->usage);
/*
* Checking that more arguments are specified in the configuration
* line than the maximum allowed.
*/
if (!(retval & ERR_CODE) && ((*pdata)->args_max > 0))
if (argc > (*pdata)->args_max)
FLT_OTEL_PARSE_ERR(err, "'%s' : too many arguments (use '%s%s')", args[0], (*pdata)->name, (*pdata)->usage);
/* Checking that the first argument has only allowed characters. */
if (!(retval & ERR_CODE) && ((*pdata)->check_name != FLT_OTEL_PARSE_INVALID_NONE)) {
const char *ic;
ic = flt_otel_parse_invalid_char(args[1], (*pdata)->check_name);
if (ic != NULL)
FLT_OTEL_PARSE_ERR(err, "%s '%s' : invalid character '%c'", args[0], args[1], *ic);
}
/* Checking that the data group name is defined. */
if (!(retval & ERR_CODE) && (*pdata)->flag_check_id && (id == NULL))
FLT_OTEL_PARSE_ERR(err, "'%s' : %s ID not set (use '%s%s')", args[0], parse_data[1].name, parse_data[1].name, parse_data[1].usage);
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse_cfg_sample_expr - sample expression parser
*
* SYNOPSIS
* static int flt_otel_parse_cfg_sample_expr(const char *file, int line, char **args, int *idx, struct list *head, char **err)
*
* ARGUMENTS
* file - configuration file path
* line - configuration file line number
* args - configuration line arguments array
* idx - pointer to the current position in <args>
* head - list head for parsed sample expressions
* err - indirect pointer to error message string
*
* DESCRIPTION
* Parses a single HAProxy sample expression at position <*idx> in <args>.
* Creates a conf_sample_expr structure and calls sample_parse_expr() to
* compile the expression.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse_cfg_sample_expr(const char *file, int line, char **args, int *idx, struct list *head, char **err)
{
struct flt_otel_conf_sample_expr *expr;
int retval = ERR_NONE;
OTELC_FUNC("\"%s\", %d, %p, %p, %p, %p:%p", OTELC_STR_ARG(file), line, args, idx, head, OTELC_DPTR_ARGS(err));
expr = flt_otel_conf_sample_expr_init(args[*idx], line, head, err);
if (expr != NULL) {
expr->expr = sample_parse_expr(args, idx, file, line, err, &(flt_otel_current_config->proxy->conf.args), NULL);
if (expr->expr != NULL)
OTELC_DBG(DEBUG, "sample expression '%s' added", expr->fmt_expr);
else
retval |= ERR_ABORT | ERR_ALERT;
} else {
retval |= ERR_ABORT | ERR_ALERT;
}
if (retval & ERR_CODE)
flt_otel_conf_sample_expr_free(&expr);
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse_cfg_sample - sample definition parser
*
* SYNOPSIS
* static int flt_otel_parse_cfg_sample(const char *file, int line, char **args, int idx, int n, const struct otelc_value *extra, struct list *head, char **err)
*
* ARGUMENTS
* file - configuration file path
* line - configuration file line number
* args - configuration line arguments array
* idx - args[] position where the sample value starts
* n - maximum number of sample expressions to parse (0 means unlimited)
* extra - optional extra data (event name or status code)
* head - list head for parsed sample definitions
* err - indirect pointer to error message string
*
* DESCRIPTION
* Parses a complete sample definition starting at index <idx> in the
* <args> array. A new conf_sample structure is allocated and initialized
* via flt_otel_conf_sample_init_ex() with the optional <extra> data (an
* event name or a status code), then the sample expressions are parsed.
*
* When <args>[<idx>] contains the "%[" sequence, the argument is parsed
* as a log-format string via parse_logformat_string(): the lf_used flag
* is set and the result is stored in the lf_expr member while the exprs
* list remains empty. Otherwise the arguments are treated as bare sample
* expressions: the proxy configuration context is set and the function
* calls flt_otel_parse_cfg_sample_expr() in a loop to populate exprs.
*
* When <n> is 0 all remaining valid arguments are consumed; otherwise at
* most <n> expressions are parsed. On error the allocated conf_sample
* structure is freed before returning.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success, or a combination of ERR_* flags
* if an error is encountered.
*/
static int flt_otel_parse_cfg_sample(const char *file, int line, char **args, int idx, int n, const struct otelc_value *extra, struct list *head, char **err)
{
struct flt_otel_conf_sample *sample;
int retval = ERR_NONE;
int count = 0;
OTELC_FUNC("\"%s\", %d, %p, %d, %d, %p, %p, %p:%p", OTELC_STR_ARG(file), line, args, idx, n, extra, head, OTELC_DPTR_ARGS(err));
sample = flt_otel_conf_sample_init_ex((const char **)args, idx, n, extra, line, head, err);
if (sample == NULL)
FLT_OTEL_PARSE_ERR(err, "'%s' : out of memory", args[0]);
if (retval & ERR_CODE) {
/* Do nothing. */
}
else if (strstr(args[idx], "%[") != NULL) {
/*
* Log-format path: parse the single argument as a log-format
* string into the sample structure.
*/
sample->lf_used = 1;
if (parse_logformat_string(args[idx], flt_otel_current_config->proxy, &(sample->lf_expr), LOG_OPT_HTTP, SMP_VAL_FE_LOG_END, err) == 0)
retval |= ERR_ABORT | ERR_ALERT;
else
OTELC_DBG(DEBUG, "sample '%s' -> log-format '%s' added", sample->key, sample->fmt_string);
}
else {
/*
* Bare sample expression path.
*/
flt_otel_current_config->proxy->conf.args.ctx = ARGC_OTEL;
flt_otel_current_config->proxy->conf.args.file = file;
flt_otel_current_config->proxy->conf.args.line = line;
while (!(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(idx) && ((n == 0) || (count < n))) {
retval = flt_otel_parse_cfg_sample_expr(file, line, args, &idx, &(sample->exprs), err);
if (!(retval & ERR_CODE))
count++;
}
flt_otel_current_config->proxy->conf.args.file = NULL;
flt_otel_current_config->proxy->conf.args.line = 0;
OTELC_DBG(DEBUG, "sample '%s' -> '%s' added, (%d %d)", sample->key, sample->fmt_string, sample->num_exprs, count);
}
if (retval & ERR_CODE)
flt_otel_conf_sample_free(&sample);
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse_cfg_str - string list parser
*
* SYNOPSIS
* static int flt_otel_parse_cfg_str(const char *file, int line, char **args, struct list *head, char **err)
*
* ARGUMENTS
* file - configuration file path
* line - configuration file line number
* args - configuration line arguments array
* head - list head for parsed string entries
* err - indirect pointer to error message string
*
* DESCRIPTION
* Parses one or more string arguments into a conf_str list. All arguments
* starting from index 1 are added to <head>. Used for the "finish" keyword.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse_cfg_str(const char *file, int line, char **args, struct list *head, char **err)
{
int i, retval = ERR_NONE;
OTELC_FUNC("\"%s\", %d, %p, %p, %p:%p", OTELC_STR_ARG(file), line, args, head, OTELC_DPTR_ARGS(err));
for (i = 1; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++)
if (flt_otel_conf_str_init(args[i], line, head, err) == NULL)
retval |= ERR_ABORT | ERR_ALERT;
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse_cfg_file - file path argument parser
*
* SYNOPSIS
* static int flt_otel_parse_cfg_file(char **ptr, const char *file, int line, char **args, char **err, const char *err_msg)
*
* ARGUMENTS
* ptr - pointer to the destination file path string pointer
* file - configuration file path
* line - configuration file line number
* args - configuration line arguments array
* err - indirect pointer to error message string
* err_msg - context label used in error messages
*
* DESCRIPTION
* Parses and validates a file path argument. Checks that the argument is
* present, that no extra arguments follow, and that the file exists and is
* readable.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse_cfg_file(char **ptr, const char *file, int line, char **args, char **err, const char *err_msg)
{
int retval = ERR_NONE;
OTELC_FUNC("%p:%p, \"%s\", %d, %p, %p:%p, \"%s\"", OTELC_DPTR_ARGS(ptr), OTELC_STR_ARG(file), line, args, OTELC_DPTR_ARGS(err), err_msg);
if (!FLT_OTEL_ARG_ISVALID(1))
FLT_OTEL_PARSE_ERR(err, "'%s' : no %s specified", flt_otel_current_instr->id, err_msg);
else if (alertif_too_many_args(1, file, line, args, &retval))
retval |= ERR_ABORT | ERR_ALERT;
else if (access(args[1], R_OK) == -1)
FLT_OTEL_PARSE_ERR(err, "'%s' : %s", args[1], strerror(errno));
else
retval = flt_otel_parse_keyword(ptr, args, 0, 0, err, err_msg);
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse_check_scope - configuration scope filter
*
* SYNOPSIS
* static bool flt_otel_parse_check_scope(void)
*
* ARGUMENTS
* This function takes no arguments.
*
* DESCRIPTION
* Checks whether the current configuration parsing is within the correct
* HAProxy cfg_scope filter. When cfg_scope is set and does not match the
* current filter ID, the configuration line is skipped.
*
* RETURN VALUE
* Returns TRUE in case the configuration is not in the currently
* defined scope, FALSE otherwise.
*/
static bool flt_otel_parse_check_scope(void)
{
bool retval = 0;
if ((cfg_scope != NULL) && (flt_otel_current_config->id != NULL) && (strcmp(flt_otel_current_config->id, cfg_scope) != 0)) {
OTELC_DBG(INFO, "cfg_scope: '%s', id: '%s'", cfg_scope, flt_otel_current_config->id);
retval = 1;
}
return retval;
}
/***
* NAME
* flt_otel_parse_cfg_instr - otel-instrumentation section parser
*
* SYNOPSIS
* static int flt_otel_parse_cfg_instr(const char *file, int line, char **args, int kw_mod)
*
* ARGUMENTS
* file - configuration file path
* line - configuration file line number
* args - configuration line arguments array
* kw_mod - keyword modifier flags (e.g. KWM_NO)
*
* DESCRIPTION
* Section parser for the otel-instrumentation configuration block. Handles
* keywords: instrumentation ID, log, config, groups, scopes, acl, rate-limit,
* option (disabled/hard-errors/nolognorm), and debug-level.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse_cfg_instr(const char *file, int line, char **args, int kw_mod)
{
#define FLT_OTEL_PARSE_INSTR_DEF(a,b,c,d,e,f,g) { FLT_OTEL_PARSE_INSTR_##a, b, FLT_OTEL_PARSE_INVALID_##c, d, e, f, g },
static const struct flt_otel_parse_data parse_data[] = { FLT_OTEL_PARSE_INSTR_DEFINES };
#undef FLT_OTEL_PARSE_INSTR_DEF
const struct flt_otel_parse_data *pdata = NULL;
char *err = NULL, *err_log = NULL;
int i, retval = ERR_NONE;
OTELC_FUNC("\"%s\", %d, %p, 0x%08x", OTELC_STR_ARG(file), line, args, kw_mod);
if (flt_otel_parse_check_scope())
OTELC_RETURN_INT(retval);
/* Validate and identify the instrumentation keyword. */
retval = flt_otel_parse_cfg_check(file, line, args, flt_otel_current_instr, parse_data, OTELC_TABLESIZE(parse_data), &pdata, &err);
if (retval & ERR_CODE) {
FLT_OTEL_PARSE_IFERR_ALERT();
OTELC_RETURN_INT(retval);
}
/* Handle keyword-specific instrumentation configuration. */
if (pdata->keyword == FLT_OTEL_PARSE_INSTR_ID) {
if (flt_otel_current_config->instr != NULL) {
FLT_OTEL_PARSE_ERR(&err, "'%s' : instrumentation can be defined only once", args[1]);
} else {
flt_otel_current_instr = flt_otel_conf_instr_init(args[1], line, NULL, &err);
if (flt_otel_current_instr == NULL)
retval |= ERR_ABORT | ERR_ALERT;
}
}
else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_LOG) {
if (parse_logger(args, &(flt_otel_current_instr->proxy_log.loggers), kw_mod == KWM_NO, file, line, &err_log) == 0) {
FLT_OTEL_PARSE_ERR(&err, "'%s %s ...' : %s", args[0], args[1], err_log);
OTELC_SFREE_CLEAR(err_log);
} else {
flt_otel_current_instr->logging |= FLT_OTEL_LOGGING_ON;
}
}
else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_CONFIG) {
retval = flt_otel_parse_cfg_file(&(flt_otel_current_instr->config), file, line, args, &err, "configuration file");
}
else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_GROUPS) {
for (i = 1; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++)
if (flt_otel_conf_ph_init(args[i], line, &(flt_otel_current_instr->ph_groups), &err) == NULL)
retval |= ERR_ABORT | ERR_ALERT;
}
else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_SCOPES) {
for (i = 1; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++)
if (flt_otel_conf_ph_init(args[i], line, &(flt_otel_current_instr->ph_scopes), &err) == NULL)
retval |= ERR_ABORT | ERR_ALERT;
}
else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_ACL) {
if (FLT_OTEL_PARSE_KEYWORD(1, "or"))
FLT_OTEL_PARSE_ERR(&err, "'%s %s ...' : invalid ACL name", args[0], args[1]);
else if (parse_acl((const char **)args + 1, &(flt_otel_current_instr->acls), &err, &(flt_otel_current_config->proxy->conf.args), file, line) == NULL)
retval |= ERR_ABORT | ERR_ALERT;
}
else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_RATE_LIMIT) {
double value;
if (flt_otel_strtod(args[1], &value, 0.0, 100.0, &err))
flt_otel_current_instr->rate_limit = FLT_OTEL_FLOAT_U32(value);
}
else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_OPTION) {
if (FLT_OTEL_PARSE_KEYWORD(1, FLT_OTEL_PARSE_OPTION_DISABLED)) {
flt_otel_current_instr->flag_disabled = (kw_mod == KWM_NO) ? 0 : 1;
}
else if (FLT_OTEL_PARSE_KEYWORD(1, FLT_OTEL_PARSE_OPTION_HARDERR)) {
flt_otel_current_instr->flag_harderr = (kw_mod == KWM_NO) ? 0 : 1;
}
else if (FLT_OTEL_PARSE_KEYWORD(1, FLT_OTEL_PARSE_OPTION_NOLOGNORM)) {
if (kw_mod == KWM_NO)
flt_otel_current_instr->logging &= ~FLT_OTEL_LOGGING_NOLOGNORM;
else
flt_otel_current_instr->logging |= FLT_OTEL_LOGGING_NOLOGNORM;
}
else
FLT_OTEL_PARSE_ERR(&err, "'%s' : unknown option '%s'", args[0], args[1]);
}
#ifdef DEBUG_OTEL
else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_DEBUG_LEVEL) {
int64_t value;
if (flt_otel_strtoll(args[1], &value, 0, OTELC_DBG_LEVEL_MASK, &err))
otelc_dbg_level = value;
}
#else
else {
FLT_OTEL_PARSE_WARNING("'%s' : keyword ignored", file, line, args[0]);
}
#endif
FLT_OTEL_PARSE_IFERR_ALERT();
if ((retval & ERR_CODE) && (flt_otel_current_instr != NULL))
flt_otel_conf_instr_free(&flt_otel_current_instr);
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_post_parse_cfg_instr - otel-instrumentation post-parse check
*
* SYNOPSIS
* static int flt_otel_post_parse_cfg_instr(void)
*
* ARGUMENTS
* This function takes no arguments.
*
* DESCRIPTION
* Post-parse callback for the otel-instrumentation section. Links the parsed
* instrumentation structure to the filter configuration and verifies that a
* configuration file path is specified.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_post_parse_cfg_instr(void)
{
int retval = ERR_NONE;
OTELC_FUNC("");
if (flt_otel_current_instr == NULL)
OTELC_RETURN_INT(retval);
flt_otel_current_config->instr = flt_otel_current_instr;
if (flt_otel_current_instr->id == NULL)
OTELC_RETURN_INT(retval);
if (flt_otel_current_instr->config == NULL)
FLT_OTEL_POST_PARSE_ALERT("instrumentation '%s' has no configuration file specified", flt_otel_current_instr->cfg_line, flt_otel_current_instr->id);
flt_otel_current_instr = NULL;
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse_cfg_group - otel-group section parser
*
* SYNOPSIS
* static int flt_otel_parse_cfg_group(const char *file, int line, char **args, int kw_mod)
*
* ARGUMENTS
* file - configuration file path
* line - configuration file line number
* args - configuration line arguments array
* kw_mod - keyword modifier flags (e.g. KWM_NO)
*
* DESCRIPTION
* Section parser for the otel-group configuration block. Handles keywords:
* group ID and scopes.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse_cfg_group(const char *file, int line, char **args, int kw_mod)
{
#define FLT_OTEL_PARSE_GROUP_DEF(a,b,c,d,e,f,g) { FLT_OTEL_PARSE_GROUP_##a, b, FLT_OTEL_PARSE_INVALID_##c, d, e, f, g },
static const struct flt_otel_parse_data parse_data[] = { FLT_OTEL_PARSE_GROUP_DEFINES };
#undef FLT_OTEL_PARSE_GROUP_DEF
const struct flt_otel_parse_data *pdata = NULL;
char *err = NULL;
int i, retval = ERR_NONE;
OTELC_FUNC("\"%s\", %d, %p, 0x%08x", OTELC_STR_ARG(file), line, args, kw_mod);
if (flt_otel_parse_check_scope())
OTELC_RETURN_INT(retval);
/* Validate and identify the group keyword. */
retval = flt_otel_parse_cfg_check(file, line, args, flt_otel_current_group, parse_data, OTELC_TABLESIZE(parse_data), &pdata, &err);
if (retval & ERR_CODE) {
FLT_OTEL_PARSE_IFERR_ALERT();
OTELC_RETURN_INT(retval);
}
/* Handle keyword-specific group configuration. */
if (pdata->keyword == FLT_OTEL_PARSE_GROUP_ID) {
flt_otel_current_group = flt_otel_conf_group_init(args[1], line, &(flt_otel_current_config->groups), &err);
if (flt_otel_current_group == NULL)
retval |= ERR_ABORT | ERR_ALERT;
}
else if (pdata->keyword == FLT_OTEL_PARSE_GROUP_SCOPES) {
for (i = 1; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++)
if (flt_otel_conf_ph_init(args[i], line, &(flt_otel_current_group->ph_scopes), &err) == NULL)
retval |= ERR_ABORT | ERR_ALERT;
}
FLT_OTEL_PARSE_IFERR_ALERT();
if ((retval & ERR_CODE) && (flt_otel_current_group != NULL))
flt_otel_conf_group_free(&flt_otel_current_group);
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_post_parse_cfg_group - otel-group post-parse check
*
* SYNOPSIS
* static int flt_otel_post_parse_cfg_group(void)
*
* ARGUMENTS
* This function takes no arguments.
*
* DESCRIPTION
* Post-parse callback for the otel-group section. Verifies that at least one
* scope is defined in the group.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_post_parse_cfg_group(void)
{
int retval = ERR_NONE;
OTELC_FUNC("");
if (flt_otel_current_group == NULL)
OTELC_RETURN_INT(retval);
/* Check that the group has at least one scope defined. */
if (LIST_ISEMPTY(&(flt_otel_current_group->ph_scopes)))
FLT_OTEL_POST_PARSE_ALERT("group '%s' has no defined scope(s)", flt_otel_current_group->cfg_line, flt_otel_current_group->id);
flt_otel_current_group = NULL;
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse_cfg_scope_ctx - context storage type parser
*
* SYNOPSIS
* static int flt_otel_parse_cfg_scope_ctx(char **args, int cur_arg, char **err)
*
* ARGUMENTS
* args - configuration line arguments array
* cur_arg - index of the storage type argument in <args>
* err - indirect pointer to error message string
*
* DESCRIPTION
* Parses the context storage type argument for inject/extract keywords.
* Accepts "use-headers" or (when USE_OTEL_VARS is defined) "use-vars".
* Both types may be used simultaneously on the same span.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse_cfg_scope_ctx(char **args, int cur_arg, char **err)
{
uint8_t flags = 0;
int retval = ERR_NONE;
OTELC_FUNC("%p, %d, %p:%p", args, cur_arg, OTELC_DPTR_ARGS(err));
if (FLT_OTEL_PARSE_KEYWORD(cur_arg, FLT_OTEL_PARSE_CTX_USE_HEADERS))
flags = FLT_OTEL_CTX_USE_HEADERS;
#ifdef USE_OTEL_VARS
else if (FLT_OTEL_PARSE_KEYWORD(cur_arg, FLT_OTEL_PARSE_CTX_USE_VARS))
flags = FLT_OTEL_CTX_USE_VARS;
#endif
else
FLT_OTEL_PARSE_ERR(err, "'%s' : invalid context storage type", args[0]);
if (flags == 0)
/* Do nothing. */;
else if (flt_otel_current_span->ctx_flags & flags)
FLT_OTEL_PARSE_ERR(err, "'%s' : %s already used", args[0], args[cur_arg]);
else
flt_otel_current_span->ctx_flags |= flags;
OTELC_DBG(NOTICE, "ctx_flags: 0x%02hhx (0x%02hhx)", flt_otel_current_span->ctx_flags, flags);
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse_acl - ACL condition builder
*
* SYNOPSIS
* static struct acl_cond *flt_otel_parse_acl(const char *file, int line, struct proxy *px, const char **args, char **err, struct list *head, ...)
*
* ARGUMENTS
* file - configuration file path
* line - configuration file line number
* px - proxy instance for ACL resolution
* args - condition arguments (if/unless followed by ACL names)
* err - indirect pointer to error message string
* head - first ACL list head to search
*
* DESCRIPTION
* Builds an ACL condition by trying multiple ACL lists in order. The
* variadic arguments provide a sequence of ACL list heads to search; the
* first successful build_acl_cond() result is returned.
*
* RETURN VALUE
* Returns a pointer to the built ACL condition, or NULL if no condition could
* be built from any of the provided lists.
*/
static struct acl_cond *flt_otel_parse_acl(const char *file, int line, struct proxy *px, const char **args, char **err, struct list *head, ...)
{
va_list ap;
int n = 0;
struct acl_cond *retptr = NULL;
OTELC_FUNC("\"%s\", %d, %p, %p, %p:%p, %p, ...", OTELC_STR_ARG(file), line, px, args, OTELC_DPTR_ARGS(err), head);
/* Try each ACL list in order until a condition is built. */
for (va_start(ap, head); (retptr == NULL) && (head != NULL); head = va_arg(ap, typeof(head)), n++) {
retptr = build_acl_cond(file, line, head, px, args, (n == 0) ? err : NULL);
if (retptr != NULL)
OTELC_DBG(NOTICE, "ACL build done, using list %p %d", head, n);
}
va_end(ap);
if ((retptr != NULL) && (err != NULL))
ha_free(err);
OTELC_RETURN_PTR(retptr);
}
/***
* NAME
* flt_otel_parse_bounds - histogram boundary string parser
*
* SYNOPSIS
* static int flt_otel_parse_bounds(const char *str, double **bounds, size_t *bounds_num, char **err, const char *err_msg)
*
* ARGUMENTS
* str - space-separated numeric boundary string
* bounds - pointer to the destination boundary array
* bounds_num - pointer to store the number of boundaries
* err - indirect pointer to error message string
* err_msg - context label used in error messages
*
* DESCRIPTION
* Parses a space-separated string of numbers into a dynamically allocated
* array of doubles suitable for the meter add_view API. The string is
* duplicated internally and tokenized with strtok(). Each token is
* converted with flt_otel_strtod(). The values are sorted internally.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse_bounds(const char *str, double **bounds, size_t *bounds_num, char **err, const char *err_msg)
{
char *buffer, *token, *lasts;
size_t bounds_len = 0, bounds_size = 8;
double value, *ptr;
int retval = ERR_NONE;
OTELC_FUNC("\"%s\", %p, %p, %p:%p, \"%s\"", OTELC_STR_ARG(str), bounds, bounds_num, OTELC_DPTR_ARGS(err), OTELC_STR_ARG(err_msg));
buffer = OTELC_STRDUP(str);
*bounds = OTELC_CALLOC(bounds_size, sizeof(**bounds));
if ((buffer == NULL) || (*bounds == NULL)) {
OTELC_SFREE(buffer);
OTELC_SFREE(*bounds);
FLT_OTEL_PARSE_ERR(err, "'%s' : out of memory", err_msg);
OTELC_RETURN_INT(retval);
}
/* Tokenize and parse space-separated boundary values. */
for (token = strtok_r(buffer, " \t", &lasts); token != NULL; token = strtok_r(NULL, " \t", &lasts)) {
if (!flt_otel_strtod(token, &value, 0.0, DBL_MAX, err)) {
retval |= ERR_ABORT | ERR_ALERT;
break;
}
else if (bounds_len >= bounds_size) {
ptr = OTELC_REALLOC(*bounds, (bounds_size + 8) * sizeof(*ptr));
if (ptr == NULL) {
FLT_OTEL_PARSE_ERR(err, "'%s' : out of memory", err_msg);
OTELC_SFREE_CLEAR(*bounds);
break;
}
*bounds = ptr;
bounds_size += 8;
}
(*bounds)[bounds_len++] = value;
}
/* Sort the bounds and reject duplicates. */
if ((*bounds != NULL) && (bounds_len > 1)) {
size_t i;
qsort(*bounds, bounds_len, sizeof(**bounds), flt_otel_qsort_compar_double);
for (i = 1; i < bounds_len; i++)
if (flt_otel_qsort_compar_double(*bounds + i - 1, *bounds + i) == 0) {
FLT_OTEL_PARSE_ERR(err, "'%s' : duplicate boundary value '%.2f'", err_msg, (*bounds)[i]);
OTELC_SFREE_CLEAR(*bounds);
break;
}
}
OTELC_SFREE(buffer);
if (*bounds == NULL) {
*bounds_num = 0;
}
else if (bounds_len == 0) {
FLT_OTEL_PARSE_ERR(err, "'%s' : empty bounds", err_msg);
OTELC_SFREE_CLEAR(*bounds);
*bounds_num = 0;
}
else {
*bounds_num = bounds_len;
}
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse_cfg_instrument - instrument keyword parser
*
* SYNOPSIS
* static int flt_otel_parse_cfg_instrument(const char *file, int line, char **args, const struct flt_otel_parse_data *pdata, char **err)
*
* ARGUMENTS
* file - configuration file path
* line - configuration file line number
* args - configuration line arguments array
* pdata - keyword metadata (name, usage, argument limits)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Parses the "instrument" keyword inside an otel-scope section. Two forms
* are supported: the "update" form that references an existing instrument by
* name and adds attributes to it, and the "create" form that defines a new
* metric instrument with a type, name, optional aggregation type (preceded by
* the 'aggr' keyword), optional description, optional unit, a single sample
* expression for the value, and optional histogram bucket boundaries
* (preceded by the 'bounds' keyword). The 'bounds' keyword is only valid for
* histogram instrument types.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse_cfg_instrument(const char *file, int line, char **args, const struct flt_otel_parse_data *pdata, char **err)
{
#define FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEF(a,b) { OTELC_METRIC_INSTRUMENT_##a, b },
static const struct {
otelc_metric_instrument_t type;
const char *keyword;
} instr_type[] = { FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEFINES };
#undef FLT_OTEL_PARSE_SCOPE_INSTRUMENT_DEF
struct flt_otel_conf_instrument *instr;
int i, retval = ERR_NONE;
OTELC_FUNC("\"%s\", %d, %p, %p, %p:%p", OTELC_STR_ARG(file), line, args, pdata, OTELC_DPTR_ARGS(err));
/* Look up the instrument type from args[1]. */
for (i = 0; i < OTELC_TABLESIZE(instr_type); i++)
if (FLT_OTEL_PARSE_KEYWORD(1, instr_type[i].keyword)) {
OTELC_DBG(DEBUG, "instrument type: %d '%s'", instr_type[i].type, instr_type[i].keyword);
break;
}
if (i >= OTELC_TABLESIZE(instr_type)) {
FLT_OTEL_PARSE_ERR(err, "'%s' : invalid instrument type", args[1]);
OTELC_RETURN_INT(retval);
}
/*
* Only one create and one update instrument per name are allowed.
* Pass NULL as head for update instruments to bypass the generic
* duplicate check (which would reject the shared name), check for
* update duplicates separately, and append to the list manually.
*/
if (instr_type[i].type == OTELC_METRIC_INSTRUMENT_UPDATE) {
list_for_each_entry(instr, &(flt_otel_current_scope->instruments), list)
if ((instr->type == OTELC_METRIC_INSTRUMENT_UPDATE) && FLT_OTEL_PARSE_KEYWORD(2, instr->id)) {
FLT_OTEL_ERR("'%s' : already defined", args[2]);
OTELC_RETURN_INT(retval);
}
instr = flt_otel_conf_instrument_init(args[2], line, NULL, err);
if (instr != NULL)
LIST_APPEND(&(flt_otel_current_scope->instruments), &(instr->list));
} else {
instr = flt_otel_conf_instrument_init(args[2], line, &(flt_otel_current_scope->instruments), err);
}
if (instr == NULL) {
retval |= ERR_ABORT | ERR_ALERT;
}
else if (instr_type[i].type == OTELC_METRIC_INSTRUMENT_UPDATE) {
bool flag_add_attr = false;
instr->type = instr_type[i].type;
/* Update instruments only accept additional attributes. */
for (i = 3; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++) {
if (flag_add_attr) {
if (!FLT_OTEL_ARG_ISVALID(i) || !FLT_OTEL_ARG_ISVALID(i + 1))
FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
else if (otelc_kv_add(&(instr->attr), &(instr->attr_len), args[i], args[i + 1], strlen(args[i + 1])) == OTELC_RET_ERROR)
FLT_OTEL_PARSE_ERR(err, "'%s' : out of memory", args[0]);
else
i++;
}
else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_INSTRUMENT_ATTR)) {
flag_add_attr = true;
}
else {
FLT_OTEL_PARSE_ERR(err, "'%s' : unknown keyword (use '%s%s')", args[i], pdata->name, pdata->usage);
}
}
if (flag_add_attr && (instr->attr_len == 0))
FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
}
else {
instr->type = instr_type[i].type;
/*
* Create instruments accept aggr, description, unit, value,
* and bounds.
*/
for (i = 3; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++) {
if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_INSTRUMENT_AGGR)) {
if (!FLT_OTEL_ARG_ISVALID(i + 1))
FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
else if (instr->aggr_type != OTELC_METRIC_AGGREGATION_UNSET)
FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
else {
otelc_metric_aggregation_type_t type = otelc_meter_aggr_parse(args[++i]);
if (type == OTELC_RET_ERROR)
FLT_OTEL_PARSE_ERR(err, "'%s' : invalid aggregation type", args[i]);
else
instr->aggr_type = type;
}
}
else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_INSTRUMENT_DESC)) {
if (!FLT_OTEL_ARG_ISVALID(i + 1))
FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
else if (instr->description == NULL)
retval = flt_otel_parse_strdup(&(instr->description), NULL, args[++i], err, args[0]);
else
FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
}
else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_INSTRUMENT_UNIT)) {
if (!FLT_OTEL_ARG_ISVALID(i + 1))
FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
else if (instr->unit == NULL)
retval = flt_otel_parse_strdup(&(instr->unit), NULL, args[++i], err, args[0]);
else
FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
}
else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_INSTRUMENT_VALUE)) {
if (!FLT_OTEL_ARG_ISVALID(i + 1))
FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
else if (!LIST_ISEMPTY(&(instr->samples)))
FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
else {
retval = flt_otel_parse_cfg_sample(file, line, args, ++i, 1, NULL, &(instr->samples), err);
if (!(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i + 1) && !FLT_OTEL_PARSE_KEYWORD(i + 1, FLT_OTEL_PARSE_INSTRUMENT_AGGR) && !FLT_OTEL_PARSE_KEYWORD(i + 1, FLT_OTEL_PARSE_INSTRUMENT_DESC) && !FLT_OTEL_PARSE_KEYWORD(i + 1, FLT_OTEL_PARSE_INSTRUMENT_UNIT) && !FLT_OTEL_PARSE_KEYWORD(i + 1, FLT_OTEL_PARSE_INSTRUMENT_VALUE) && !FLT_OTEL_PARSE_KEYWORD(i + 1, FLT_OTEL_PARSE_INSTRUMENT_BOUNDS))
FLT_OTEL_PARSE_ERR(err, "'%s' : only one sample expression allowed per instrument", args[0]);
}
}
else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_INSTRUMENT_BOUNDS)) {
if (!FLT_OTEL_ARG_ISVALID(i + 1))
FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
else if (instr->type != OTELC_METRIC_INSTRUMENT_HISTOGRAM_UINT64)
FLT_OTEL_PARSE_ERR(err, "'%s' : bounds only valid for hist_int instruments", args[i]);
else if (instr->bounds != NULL)
FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
else
retval = flt_otel_parse_bounds(args[++i], &(instr->bounds), &(instr->bounds_num), err, args[0]);
}
else {
FLT_OTEL_PARSE_ERR(err, "'%s' : invalid argument (use '%s%s')", args[i], pdata->name, pdata->usage);
}
}
}
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse_cfg_log_record - log-record keyword parser
*
* SYNOPSIS
* static int flt_otel_parse_cfg_log_record(const char *file, int line, char **args, const struct flt_otel_parse_data *pdata, char **err)
*
* ARGUMENTS
* file - configuration file path
* line - configuration file line number
* args - configuration line arguments array
* pdata - keyword metadata (name, usage, argument limits)
* err - indirect pointer to error message string
*
* DESCRIPTION
* Parses the "log-record" keyword inside an otel-scope section. The first
* argument is a required severity level string. Optional keywords "id",
* "event", "span", and "attr" follow in any order. The remaining arguments
* at the end are parsed as fetch expressions or a log-format string.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse_cfg_log_record(const char *file, int line, char **args, const struct flt_otel_parse_data *pdata, char **err)
{
struct flt_otel_conf_log_record *log;
otelc_log_severity_t severity;
int i, retval = ERR_NONE;
OTELC_FUNC("\"%s\", %d, %p, %p, %p:%p", OTELC_STR_ARG(file), line, args, pdata, OTELC_DPTR_ARGS(err));
/* Look up the severity level from args[1]. */
severity = otelc_logger_severity_parse(args[1]);
if (severity == OTELC_LOG_SEVERITY_INVALID) {
FLT_OTEL_PARSE_ERR(err, "'%s' : invalid log severity", args[1]);
OTELC_RETURN_INT(retval);
}
log = flt_otel_conf_log_record_init(FLT_OTEL_CONF_HDR_SPECIAL "log-record", line, &(flt_otel_current_scope->log_records), err);
if (log == NULL) {
retval |= ERR_ABORT | ERR_ALERT;
OTELC_RETURN_INT(retval);
}
log->severity = severity;
/* Parse optional keywords starting from args[2]. */
for (i = 2; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++) {
if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_LOG_RECORD_ID)) {
if (!FLT_OTEL_ARG_ISVALID(i + 1))
FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
else if (log->event_id != 0)
FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
else if (!flt_otel_strtoll(args[++i], &(log->event_id), 0, LLONG_MAX, err))
retval |= ERR_ABORT | ERR_ALERT;
}
else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_LOG_RECORD_EVENT)) {
if (!FLT_OTEL_ARG_ISVALID(i + 1))
FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
else if (log->event_name != NULL)
FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
else
retval = flt_otel_parse_strdup(&(log->event_name), NULL, args[++i], err, args[0]);
}
else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_LOG_RECORD_SPAN)) {
if (!FLT_OTEL_ARG_ISVALID(i + 1))
FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
else if (log->span != NULL)
FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
else
retval = flt_otel_parse_strdup(&(log->span), NULL, args[++i], err, args[0]);
}
else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_LOG_RECORD_ATTR)) {
if (!FLT_OTEL_ARG_ISVALID(i + 1) || !FLT_OTEL_ARG_ISVALID(i + 2))
FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
else if (otelc_kv_add(&(log->attr), &(log->attr_len), args[i + 1], args[i + 2], strlen(args[i + 2])) == OTELC_RET_ERROR)
FLT_OTEL_PARSE_ERR(err, "'%s' : out of memory", args[0]);
else
i += 2;
}
else {
/*
* Not a recognized keyword -- the remaining arguments
* are sample fetch expressions or a log-format string.
*/
retval = flt_otel_parse_cfg_sample(file, line, args, i, 0, NULL, &(log->samples), err);
break;
}
}
if (!(retval & ERR_CODE) && LIST_ISEMPTY(&(log->samples)))
FLT_OTEL_PARSE_ERR(err, "'%s' : missing body expression (use '%s%s')", args[0], pdata->name, pdata->usage);
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse_cfg_scope - otel-scope section parser
*
* SYNOPSIS
* static int flt_otel_parse_cfg_scope(const char *file, int line, char **args, int kw_mod)
*
* ARGUMENTS
* file - configuration file path
* line - configuration file line number
* args - configuration line arguments array
* kw_mod - keyword modifier flags (e.g. KWM_NO)
*
* DESCRIPTION
* Section parser for the otel-scope configuration block. Handles keywords:
* scope ID, span (with optional root/parent/link modifiers), link, attribute,
* event, baggage, status, inject, extract, finish, instrument, log-record,
* acl, and otel-event (with optional if/unless conditions).
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse_cfg_scope(const char *file, int line, char **args, int kw_mod)
{
#define FLT_OTEL_PARSE_SCOPE_DEF(a,b,c,d,e,f,g) { FLT_OTEL_PARSE_SCOPE_##a, b, FLT_OTEL_PARSE_INVALID_##c, d, e, f, g },
static const struct flt_otel_parse_data parse_data[] = { FLT_OTEL_PARSE_SCOPE_DEFINES };
#undef FLT_OTEL_PARSE_SCOPE_DEF
const struct flt_otel_parse_data *pdata = NULL;
char *err = NULL;
int i, retval = ERR_NONE;
OTELC_FUNC("\"%s\", %d, %p, 0x%08x", OTELC_STR_ARG(file), line, args, kw_mod);
if (flt_otel_parse_check_scope())
OTELC_RETURN_INT(retval);
/* Validate and identify the scope keyword. */
retval = flt_otel_parse_cfg_check(file, line, args, flt_otel_current_span, parse_data, OTELC_TABLESIZE(parse_data), &pdata, &err);
if (retval & ERR_CODE) {
FLT_OTEL_PARSE_IFERR_ALERT();
OTELC_RETURN_INT(retval);
}
/* Handle keyword-specific scope configuration. */
if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_ID) {
/* Initialization of a new scope. */
flt_otel_current_scope = flt_otel_conf_scope_init(args[1], line, &(flt_otel_current_config->scopes), &err);
if (flt_otel_current_scope == NULL)
retval |= ERR_ABORT | ERR_ALERT;
}
else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_SPAN) {
/*
* Checking if this is the beginning of the definition of
* a new span.
*/
if (flt_otel_current_span != NULL) {
OTELC_DBG(DEBUG, "span '%s' (done)", flt_otel_current_span->id);
flt_otel_current_span = NULL;
}
/* Initialization of a new span. */
flt_otel_current_span = flt_otel_conf_span_init(args[1], line, &(flt_otel_current_scope->spans), &err);
/*
* In case the span has a defined reference (parent), the
* correctness of the arguments is checked here.
*/
if (flt_otel_current_span == NULL) {
retval |= ERR_ABORT | ERR_ALERT;
}
else if (FLT_OTEL_ARG_ISVALID(2)) {
for (i = 2; (i < pdata->args_max) && FLT_OTEL_ARG_ISVALID(i); i++) {
if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_SPAN_ROOT)) {
if (flt_otel_current_span->flag_root)
FLT_OTEL_PARSE_ERR(&err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
else
flt_otel_current_span->flag_root = 1;
}
else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_SPAN_PARENT)) {
if (FLT_OTEL_ARG_ISVALID(i + 1))
retval |= flt_otel_parse_strdup(&(flt_otel_current_span->ref_id), &(flt_otel_current_span->ref_id_len), args[++i], &err, args[1]);
else
FLT_OTEL_PARSE_ERR(&err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
}
else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_SPAN_LINK)) {
if (FLT_OTEL_ARG_ISVALID(i + 1)) {
if (flt_otel_conf_link_init(args[++i], line, &(flt_otel_current_span->links), &err) == NULL)
retval |= ERR_ABORT | ERR_ALERT;
} else {
FLT_OTEL_PARSE_ERR(&err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
}
}
else {
FLT_OTEL_PARSE_ERR(&err, "'%s' : invalid argument (use '%s%s')", args[i], pdata->name, pdata->usage);
}
}
}
else {
/*
* This is not a faulty configuration, only such a case
* will be logged.
*/
OTELC_DBG(DEBUG, "new span '%s' without reference", flt_otel_current_span->id);
}
}
else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_LINK) {
for (i = 1; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++)
if (flt_otel_conf_link_init(args[i], line, &(flt_otel_current_span->links), &err) == NULL)
retval |= ERR_ABORT | ERR_ALERT;
}
else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_ATTRIBUTE) {
retval = flt_otel_parse_cfg_sample(file, line, args, 2, 0, NULL, &(flt_otel_current_span->attributes), &err);
}
else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_EVENT) {
struct otelc_value extra = { .u_type = OTELC_VALUE_STRING, .u.value_string = args[1] };
retval = flt_otel_parse_cfg_sample(file, line, args, 3, 0, &extra, &(flt_otel_current_span->events), &err);
}
else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_BAGGAGE) {
retval = flt_otel_parse_cfg_sample(file, line, args, 2, 0, NULL, &(flt_otel_current_span->baggages), &err);
}
else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_STATUS) {
#define FLT_OTEL_PARSE_SCOPE_STATUS_DEF(a,b) { OTELC_SPAN_STATUS_##a, b },
static const struct {
int code;
const char *keyword;
} status[] = { FLT_OTEL_PARSE_SCOPE_STATUS_DEFINES };
#undef FLT_OTEL_PARSE_SCOPE_STATUS_DEF
for (i = 0; i < OTELC_TABLESIZE(status); i++)
if (FLT_OTEL_PARSE_KEYWORD(1, status[i].keyword)) {
OTELC_DBG(DEBUG, "span status: %d '%s'", status[i].code, status[i].keyword);
break;
}
/*
* Regardless of the use of the list, only one status per event
* is allowed.
*/
if (i >= OTELC_TABLESIZE(status)) {
FLT_OTEL_PARSE_ERR(&err, "'%s' : invalid span status", args[1]);
}
else if (LIST_ISEMPTY(&(flt_otel_current_span->statuses))) {
struct otelc_value extra = { .u_type = OTELC_VALUE_INT32, .u.value_int32 = status[i].code };
retval = flt_otel_parse_cfg_sample(file, line, args, 2, 0, &extra, &(flt_otel_current_span->statuses), &err);
}
else {
FLT_OTEL_PARSE_ERR(&err, "only one status per event is allowed");
}
}
else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_INJECT) {
/*
* Automatic context name generation can be specified here if
* the contents of the FLT_OTEL_PARSE_CTX_AUTONAME macro are
* used as the name. In that case, if the context is after a
* particular event, it gets its name; otherwise it gets the
* name of the current span.
*/
if (flt_otel_current_span->ctx_id != NULL)
FLT_OTEL_PARSE_ERR(&err, "'%s' : only one context per span is allowed", args[1]);
else if (!FLT_OTEL_PARSE_KEYWORD(1, FLT_OTEL_PARSE_CTX_AUTONAME))
retval = flt_otel_parse_strdup(&(flt_otel_current_span->ctx_id), &(flt_otel_current_span->ctx_id_len), args[1], &err, args[0]);
else if (flt_otel_current_scope->event != FLT_OTEL_EVENT__NONE)
retval = flt_otel_parse_strdup(&(flt_otel_current_span->ctx_id), &(flt_otel_current_span->ctx_id_len), flt_otel_event_data[flt_otel_current_scope->event].name, &err, args[0]);
else {
const char *ch;
ch = invalid_prefix_char(flt_otel_current_span->id);
if (ch == NULL)
retval = flt_otel_parse_strdup(&(flt_otel_current_span->ctx_id), &(flt_otel_current_span->ctx_id_len), flt_otel_current_span->id, &err, args[0]);
else
FLT_OTEL_PARSE_ERR(&err, "'%s' : character '%c' is not permitted in the context name", flt_otel_current_span->id, *ch);
}
if (flt_otel_current_span->ctx_id != NULL) {
/*
* Here is checked the context storage type; which, if
* not explicitly specified, is set to HTTP headers.
*
* It is possible to use both types of context storage
* at the same time.
*/
if (FLT_OTEL_ARG_ISVALID(2)) {
retval |= flt_otel_parse_cfg_scope_ctx(args, 2, &err);
if (!(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(3))
retval |= flt_otel_parse_cfg_scope_ctx(args, 3, &err);
} else {
flt_otel_current_span->ctx_flags = FLT_OTEL_CTX_USE_HEADERS;
}
}
}
else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_EXTRACT) {
struct flt_otel_conf_context *conf_ctx;
/*
* Here is checked the context storage type; which, if
* not explicitly specified, is set to HTTP headers.
*/
conf_ctx = flt_otel_conf_context_init(args[1], line, &(flt_otel_current_scope->contexts), &err);
if (conf_ctx == NULL)
retval |= ERR_ABORT | ERR_ALERT;
else if (!FLT_OTEL_ARG_ISVALID(2))
conf_ctx->flags = FLT_OTEL_CTX_USE_HEADERS;
else if (FLT_OTEL_PARSE_KEYWORD(2, FLT_OTEL_PARSE_CTX_USE_HEADERS))
conf_ctx->flags = FLT_OTEL_CTX_USE_HEADERS;
#ifdef USE_OTEL_VARS
else if (FLT_OTEL_PARSE_KEYWORD(2, FLT_OTEL_PARSE_CTX_USE_VARS))
conf_ctx->flags = FLT_OTEL_CTX_USE_VARS;
#endif
else
FLT_OTEL_PARSE_ERR(&err, "'%s' : invalid context storage type", args[2]);
}
else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_FINISH) {
retval = flt_otel_parse_cfg_str(file, line, args, &(flt_otel_current_scope->spans_to_finish), &err);
}
else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_INSTRUMENT) {
retval = flt_otel_parse_cfg_instrument(file, line, args, pdata, &err);
}
else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_LOG_RECORD) {
retval = flt_otel_parse_cfg_log_record(file, line, args, pdata, &err);
}
else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_ACL) {
if (FLT_OTEL_PARSE_KEYWORD(1, "or"))
FLT_OTEL_PARSE_ERR(&err, "'%s %s ...' : invalid ACL name", args[0], args[1]);
else if (parse_acl((const char **)args + 1, &(flt_otel_current_scope->acls), &err, &(flt_otel_current_config->proxy->conf.args), file, line) == NULL)
retval |= ERR_ABORT | ERR_ALERT;
}
else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_IDLE_TIMEOUT) {
const char *res;
uint timeout;
res = parse_time_err(args[1], &timeout, TIME_UNIT_MS);
if (res == PARSE_TIME_OVER)
FLT_OTEL_PARSE_ERR(&err, "'%s' : timer overflow in argument '%s'", args[0], args[1]);
else if (res == PARSE_TIME_UNDER)
FLT_OTEL_PARSE_ERR(&err, "'%s' : timer underflow in argument '%s'", args[0], args[1]);
else if (res != NULL)
FLT_OTEL_PARSE_ERR(&err, "'%s' : unexpected character '%c' in '%s'", args[0], *res, args[1]);
else if (timeout == 0)
FLT_OTEL_PARSE_ERR(&err, "'%s' : value must be greater than zero", args[0]);
else
flt_otel_current_scope->idle_timeout = timeout;
}
else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_ON_EVENT) {
/* Scope can only have one event defined. */
if (flt_otel_current_scope->event != FLT_OTEL_EVENT__NONE) {
FLT_OTEL_PARSE_ERR(&err, "'%s' : event already set", flt_otel_current_scope->id);
} else {
/* Check the event name. */
for (i = 0; i < OTELC_TABLESIZE(flt_otel_event_data); i++)
if (FLT_OTEL_PARSE_KEYWORD(1, flt_otel_event_data[i].name)) {
flt_otel_current_scope->event = i;
break;
}
/*
* The event can have some condition defined and this
* is checked here.
*/
if (flt_otel_current_scope->event == FLT_OTEL_EVENT__NONE) {
FLT_OTEL_PARSE_ERR(&err, "'%s' : unknown event", args[1]);
}
else if (!FLT_OTEL_ARG_ISVALID(2)) {
/* Do nothing. */
}
else if (FLT_OTEL_PARSE_KEYWORD(2, FLT_OTEL_CONDITION_IF) || FLT_OTEL_PARSE_KEYWORD(2, FLT_OTEL_CONDITION_UNLESS)) {
/*
* We will first try to build ACL condition using
* local settings and then if that fails, using
* global settings (from instrumentation block).
* If it also fails, then try to use ACL defined
* in the HAProxy configuration.
*/
if (flt_otel_current_config->instr == NULL) {
FLT_OTEL_PARSE_ERR(&err, "'%s' : instrumentation not defined", args[1]);
} else {
flt_otel_current_scope->cond = flt_otel_parse_acl(file, line, flt_otel_current_config->proxy, (const char **)args + 2, &err, &(flt_otel_current_scope->acls), &(flt_otel_current_config->instr->acls), &(flt_otel_current_config->proxy->acl), NULL);
if (flt_otel_current_scope->cond == NULL)
retval |= ERR_ABORT | ERR_ALERT;
}
}
else {
FLT_OTEL_PARSE_ERR(&err, "'%s' : expects either 'if' or 'unless' followed by a condition but found '%s'", args[1], args[2]);
}
if (!(retval & ERR_CODE))
OTELC_DBG(DEBUG, "event '%s'", args[1]);
}
}
FLT_OTEL_PARSE_IFERR_ALERT();
if ((retval & ERR_CODE) && (flt_otel_current_scope != NULL)) {
flt_otel_conf_scope_free(&flt_otel_current_scope);
flt_otel_current_span = NULL;
}
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_post_parse_cfg_scope - otel-scope post-parse check
*
* SYNOPSIS
* static int flt_otel_post_parse_cfg_scope(void)
*
* ARGUMENTS
* This function takes no arguments.
*
* DESCRIPTION
* Post-parse callback for the otel-scope section. Verifies that HTTP header
* injection is only used on events that support it.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_post_parse_cfg_scope(void)
{
struct flt_otel_conf_span *conf_span;
int retval = ERR_NONE;
OTELC_FUNC("");
if (flt_otel_current_scope == NULL)
OTELC_RETURN_INT(retval);
/* If span context inject is used, check that this is possible. */
list_for_each_entry(conf_span, &(flt_otel_current_scope->spans), list)
if ((conf_span->ctx_id != NULL) && (conf_span->ctx_flags & FLT_OTEL_CTX_USE_HEADERS))
if (!flt_otel_event_data[flt_otel_current_scope->event].flag_http_inject)
FLT_OTEL_POST_PARSE_ALERT("inject '%s' : cannot use on this event", conf_span->cfg_line, conf_span->ctx_id);
/* Validate idle-timeout / on-idle-timeout consistency. */
if (flt_otel_current_scope->idle_timeout == 0) {
if (flt_otel_current_scope->event == FLT_OTEL_EVENT__IDLE_TIMEOUT)
FLT_OTEL_POST_PARSE_ALERT("'%s' : 'idle-timeout' is required for event 'on-idle-timeout'", flt_otel_current_scope->cfg_line, flt_otel_current_scope->id);
}
else if (flt_otel_current_scope->event != FLT_OTEL_EVENT__IDLE_TIMEOUT) {
FLT_OTEL_POST_PARSE_ALERT("'%s' : 'idle-timeout' can only be used with event 'on-idle-timeout'", flt_otel_current_scope->cfg_line, flt_otel_current_scope->id);
}
if (retval & ERR_CODE)
flt_otel_conf_scope_free(&flt_otel_current_scope);
flt_otel_current_scope = NULL;
flt_otel_current_span = NULL;
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse_cfg - OTel configuration file parser
*
* SYNOPSIS
* static int flt_otel_parse_cfg(struct flt_otel_conf *conf, const char *flt_name, char **err)
*
* ARGUMENTS
* conf - pointer to the filter configuration structure
* flt_name - filter name for error reporting
* err - indirect pointer to error message string
*
* DESCRIPTION
* Parses the OTel filter configuration file. Backs up the current HAProxy
* section parsers, registers temporary otel-instrumentation, otel-group, and
* otel-scope section parsers, loads and parses the file, then restores the
* original sections.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse_cfg(struct flt_otel_conf *conf, const char *flt_name, char **err)
{
struct list backup_sections;
struct cfgfile cfg_file;
int retval = ERR_ABORT | ERR_ALERT;
OTELC_FUNC("%p, \"%s\", %p:%p", conf, OTELC_STR_ARG(flt_name), OTELC_DPTR_ARGS(err));
flt_otel_current_config = conf;
/* Backup sections. */
LIST_INIT(&backup_sections);
cfg_backup_sections(&backup_sections);
/* Register new OTEL sections and parse the OTEL filter configuration file. */
if (!cfg_register_section(FLT_OTEL_PARSE_SECTION_INSTR_ID, flt_otel_parse_cfg_instr, flt_otel_post_parse_cfg_instr))
/* Do nothing. */;
else if (!cfg_register_section(FLT_OTEL_PARSE_SECTION_GROUP_ID, flt_otel_parse_cfg_group, flt_otel_post_parse_cfg_group))
/* Do nothing. */;
else if (!cfg_register_section(FLT_OTEL_PARSE_SECTION_SCOPE_ID, flt_otel_parse_cfg_scope, flt_otel_post_parse_cfg_scope))
/* Do nothing. */;
else if (access(conf->cfg_file, R_OK) == -1)
FLT_OTEL_PARSE_ERR(err, "'%s' : %s", conf->cfg_file, strerror(errno));
else {
struct list saved_args = LIST_HEAD_INIT(saved_args);
/*
* Sample fetch arguments queued during parsing are normally
* resolved by smp_resolve_args() in the proxy
* post-configuration phase. That call uses the proxy's own
* capabilities, so backend-only fetches like be_conn would
* fail when the filter is attached to a frontend.
*
* The OTel filter spans both request and response channels,
* so its sample fetches must be resolved with full FE+BE
* capabilities. To achieve this the proxy's arg list is saved
* and replaced with a fresh one before parsing. The OTel
* config parser adds only ARGC_OTEL entries to the new list.
* After parsing, those entries are moved to conf->smp_args and
* resolved later in flt_otel_check(), which runs after all
* configuration sections have been parsed so that backends and
* servers are available.
*/
LIST_SPLICE(&saved_args, &(conf->proxy->conf.args.list));
LIST_INIT(&(conf->proxy->conf.args.list));
(void)memset(&cfg_file, 0, sizeof(cfg_file));
cfg_file.filename = conf->cfg_file;
cfg_file.size = load_cfg_in_mem(cfg_file.filename, &(cfg_file.content));
if (cfg_file.size >= 0)
retval = parse_cfg(&cfg_file);
ha_free(&(cfg_file.content));
/* Stash OTEL args for deferred resolution. */
LIST_SPLICE(&(conf->smp_args), &(conf->proxy->conf.args.list));
LIST_INIT(&(conf->proxy->conf.args.list));
/* Restore the original arg list unchanged. */
LIST_SPLICE(&(conf->proxy->conf.args.list), &saved_args);
}
/* Unregister OTEL sections and restore previous sections. */
cfg_unregister_sections();
cfg_restore_sections(&backup_sections);
flt_otel_current_config = NULL;
OTELC_RETURN_INT(retval);
}
/***
* NAME
* flt_otel_parse - main filter parser entry point
*
* SYNOPSIS
* static int flt_otel_parse(char **args, int *cur_arg, struct proxy *px, struct flt_conf *fconf, char **err, void *private)
*
* ARGUMENTS
* args - configuration line arguments array
* cur_arg - pointer to the current argument index
* px - proxy instance owning the filter
* fconf - filter configuration structure to populate
* err - indirect pointer to error message string
* private - unused private data pointer
*
* DESCRIPTION
* Main filter parser entry point, registered for the "otel" filter keyword.
* Verifies that insecure-fork-wanted is enabled, then parses the filter ID
* and configuration file path from the HAProxy configuration line. If no
* filter ID is specified, the default ID is used.
*
* RETURN VALUE
* Returns ERR_NONE (== 0) in case of success,
* or a combination of ERR_* flags if an error is encountered.
*/
static int flt_otel_parse(char **args, int *cur_arg, struct proxy *px, struct flt_conf *fconf, char **err, void *private)
{
struct flt_otel_conf *conf = NULL;
int pos, retval = ERR_NONE;
OTELC_FUNC("%p, %p, %p, %p, %p:%p, %p", args, cur_arg, px, fconf, OTELC_DPTR_ARGS(err), private);
if (!(global.tune.options & GTUNE_INSECURE_FORK)) {
FLT_OTEL_PARSE_ERR(err, "The 'insecure-fork-wanted' option must be enabled in the HAProxy configuration because the OpenTelemetry filter cannot work properly if the creation of threads is forbidden.");
OTELC_RETURN_INT(retval);
}
OTELC_DBG_IFDEF(otelc_dbg_level = FLT_OTEL_DEBUG_LEVEL, );
#ifdef OTELC_DBG_MEM
/* Initialize the debug memory tracker before the first allocation. */
FLT_OTEL_RUN_ONCE(
if (otelc_dbg_mem_init(&dbg_mem, dbg_mem_data, OTELC_TABLESIZE(dbg_mem_data)) == -1) {
FLT_OTEL_PARSE_ERR(err, "cannot initialize memory debugger");
OTELC_RETURN_INT(retval);
}
);
#endif
FLT_OTEL_ARGS_DUMP();
conf = flt_otel_conf_init(px);
if (conf == NULL) {
FLT_OTEL_PARSE_ERR(err, "'%s' : out of memory", args[*cur_arg]);
OTELC_RETURN_INT(retval);
}
/* Process filter option key-value pairs. */
for (pos = *cur_arg + 1; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(pos); pos++) {
OTELC_DBG(DEBUG, "args[%d:2]: { '%s' '%s' }", pos, args[pos], args[pos + 1]);
if (FLT_OTEL_PARSE_KEYWORD(pos, FLT_OTEL_OPT_FILTER_ID)) {
retval = flt_otel_parse_keyword(&(conf->id), args, *cur_arg, pos, err, "name");
pos++;
}
else if (FLT_OTEL_PARSE_KEYWORD(pos, FLT_OTEL_OPT_CONFIG)) {
retval = flt_otel_parse_keyword(&(conf->cfg_file), args, *cur_arg, pos, err, "configuration file");
if (!(retval & ERR_CODE))
retval = flt_otel_parse_cfg(conf, args[*cur_arg], err);
pos++;
}
else {
FLT_OTEL_PARSE_ERR(err, "'%s' : unknown keyword '%s'", args[*cur_arg], args[pos]);
}
}
/* If the OpenTelemetry filter ID is not set, use default name. */
if (!(retval & ERR_CODE) && (conf->id == NULL)) {
ha_warning("parsing : " FLT_OTEL_FMT_TYPE FLT_OTEL_FMT_NAME "'no filter id set, using default id '%s'\n", FLT_OTEL_OPT_FILTER_ID_DEFAULT);
retval = flt_otel_parse_strdup(&(conf->id), NULL, FLT_OTEL_OPT_FILTER_ID_DEFAULT, err, args[*cur_arg]);
}
if (!(retval & ERR_CODE) && (conf->cfg_file == NULL))
FLT_OTEL_PARSE_ERR(err, "'%s' : no configuration file specified", args[*cur_arg]);
if (retval & ERR_CODE) {
flt_otel_conf_free(&conf);
} else {
fconf->id = otel_flt_id;
fconf->ops = &flt_otel_ops;
fconf->conf = conf;
*cur_arg = pos;
OTELC_DBG(DEBUG, "filter set: id '%s', config '%s'", conf->id, conf->cfg_file);
FLT_OTEL_DBG_CONF("- conf ", (typeof(conf))fconf->conf);
}
OTELC_RETURN_INT(retval);
}
/* Declare the filter parser for FLT_OTEL_OPT_NAME keyword. */
static struct flt_kw_list flt_kws = { FLT_OTEL_SCOPE, { }, {
{ FLT_OTEL_OPT_NAME, flt_otel_parse, NULL },
{ NULL, NULL, NULL },
}
};
INITCALL1(STG_REGISTER, flt_register_keywords, &flt_kws);
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/