mirror of
https://github.com/haproxy/haproxy.git
synced 2026-04-15 21:59:41 -04:00
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.
1826 lines
66 KiB
C
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
|
|
*/
|