mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2026-01-14 21:31:00 +01:00
MINOR: vars: implement dump_all_vars() sample fetch
This patch implements dump_all_vars([scope],[prefix]) sample fetch
function that dumps all variables in a given scope, optionally
filtered by name prefix.
Output format: var1=value1, var2=value2, ...
- String values are quoted and escaped (", , \r, \n, \b, \0)
- All sample types are supported via sample_convert()
- Scope can be: sess, txn, req, res, proc
- Prefix filtering is optional
Example usage:
http-request return string %[dump_all_vars(txn)]
http-request return string %[dump_all_vars(txn,user)]
This addresses GitHub issue #1623.
This commit is contained in:
parent
079bc39645
commit
bd1807aa3a
230
src/vars.c
230
src/vars.c
@ -58,6 +58,16 @@ static struct var_set_condition conditions_array[] = {
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
/* Variable scope names with their prefixes for output */
|
||||
static const char *var_scope_names[] = {
|
||||
[SCOPE_SESS] = "sess.",
|
||||
[SCOPE_TXN] = "txn.",
|
||||
[SCOPE_REQ] = "req.",
|
||||
[SCOPE_RES] = "res.",
|
||||
[SCOPE_PROC] = "proc.",
|
||||
[SCOPE_CHECK] = "check.",
|
||||
};
|
||||
|
||||
/* returns the struct vars pointer for a session, stream and scope, or NULL if
|
||||
* it does not exist.
|
||||
*/
|
||||
@ -352,6 +362,171 @@ static int smp_fetch_var(const struct arg *args, struct sample *smp, const char
|
||||
return vars_get_by_desc(var_desc, smp, def);
|
||||
}
|
||||
|
||||
/* Dumps all variables in the specified scope, optionally filtered by prefix.
|
||||
* Output format: var1=value1, var2=value2, ...
|
||||
* String values are quoted and escaped, binary values are hex-encoded (x...).
|
||||
* Returns 1 on success, 0 on failure (buffer too small).
|
||||
* Note: When using prefix filtering, all variables are still visited, so this
|
||||
* should not be used with configs involving thousands of variables.
|
||||
*/
|
||||
static int smp_fetch_dump_all_vars(const struct arg *args, struct sample *smp, const char *kw, void *private)
|
||||
{
|
||||
struct buffer *output;
|
||||
struct vars *vars;
|
||||
struct var *var;
|
||||
struct var_desc desc;
|
||||
const char *prefix = NULL;
|
||||
size_t prefix_len = 0;
|
||||
const char *delim = ", ";
|
||||
size_t delim_len = 2;
|
||||
int first = 1;
|
||||
int i;
|
||||
|
||||
/* Get output buffer */
|
||||
output = get_trash_chunk();
|
||||
chunk_reset(output);
|
||||
|
||||
/* Parse arguments */
|
||||
if (args[0].type == ARGT_SINT) {
|
||||
desc.scope = args[0].data.sint;
|
||||
} else {
|
||||
/* Auto-detect scope from context*/
|
||||
if (smp->strm)
|
||||
desc.scope = SCOPE_TXN;
|
||||
else if (smp->sess)
|
||||
desc.scope = SCOPE_SESS;
|
||||
else
|
||||
desc.scope = SCOPE_PROC;
|
||||
}
|
||||
|
||||
/* Optional prefix filter */
|
||||
if (args[1].type == ARGT_STR) {
|
||||
prefix = args[1].data.str.area;
|
||||
prefix_len = args[1].data.str.data;
|
||||
}
|
||||
|
||||
/* Optional delimiter */
|
||||
if (args[2].type == ARGT_STR) {
|
||||
delim = args[2].data.str.area;
|
||||
delim_len = args[2].data.str.data;
|
||||
}
|
||||
|
||||
desc.flags = 0;
|
||||
desc.name_hash = 0;
|
||||
|
||||
vars = get_vars(smp->sess, smp->strm, &desc);
|
||||
if (!vars || vars->scope != desc.scope)
|
||||
return 0;
|
||||
|
||||
vars_rdlock(vars);
|
||||
|
||||
/* Iterate through all variable roots */
|
||||
for (i = 0; i < VAR_NAME_ROOTS; i++) {
|
||||
var = cebu64_item_first(&vars->name_root[i], name_node, name_hash, struct var);
|
||||
|
||||
while (var) {
|
||||
const char *scope_prefix;
|
||||
|
||||
/* Check prefix filter */
|
||||
if (prefix) {
|
||||
if (!var->name || strncmp(var->name, prefix, prefix_len) != 0) {
|
||||
var = cebu64_item_next(&vars->name_root[i], name_node, name_hash, var);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add delimiter */
|
||||
if (!first) {
|
||||
if (output->data + delim_len >= output->size)
|
||||
goto fail_unlock;
|
||||
chunk_memcat(output, delim, delim_len);
|
||||
}
|
||||
first = 0;
|
||||
|
||||
/* Add variable name with scope prefix */
|
||||
scope_prefix = var_scope_names[desc.scope];
|
||||
if (var->name) {
|
||||
if (chunk_appendf(output, "%s%s=", scope_prefix, var->name) < 0)
|
||||
goto fail_unlock;
|
||||
} else {
|
||||
if (chunk_appendf(output, "var_%016llx=", (unsigned long long)var->name_hash) < 0)
|
||||
goto fail_unlock;
|
||||
}
|
||||
|
||||
/* Convert value based on type */
|
||||
if (var->data.type == SMP_T_STR) {
|
||||
/* String: quote and escape */
|
||||
if (chunk_escape_string(output, var->data.u.str.area, var->data.u.str.data) < 0)
|
||||
goto fail_unlock;
|
||||
|
||||
} else if (var-> data.type == SMP_T_BIN) {
|
||||
/* Binary: hex encode */
|
||||
if (dump_binary(output, var->data.u.str.area, var->data.u.str.data) != var->data.u.str.data)
|
||||
goto fail_unlock;
|
||||
} else if (var->data.type == SMP_T_SINT) {
|
||||
/* Integer */
|
||||
if (chunk_appendf(output, "%lld", (long long)var->data.u.sint) < 0)
|
||||
goto fail_unlock;
|
||||
|
||||
} else if (var->data.type == SMP_T_BOOL) {
|
||||
/* Boolean */
|
||||
const char *bool_str = var->data.u.sint ? "true" : "false";
|
||||
if (chunk_appendf(output, "%s", bool_str) < 0)
|
||||
goto fail_unlock;
|
||||
|
||||
} else if (var->data.type == SMP_T_IPV4 || var->data.type == SMP_T_IPV6) {
|
||||
/* Address */
|
||||
char addr_str[INET6_ADDRSTRLEN];
|
||||
const char *res;
|
||||
|
||||
if (var->data.type == SMP_T_IPV4)
|
||||
res = inet_ntop(AF_INET, &var->data.u.ipv4, addr_str, sizeof(addr_str));
|
||||
else
|
||||
res = inet_ntop(AF_INET6, &var->data.u.ipv6, addr_str, sizeof(addr_str));
|
||||
|
||||
if (!res) {
|
||||
if (chunk_appendf(output, "(addr)") < 0)
|
||||
goto fail_unlock;
|
||||
} else {
|
||||
if (chunk_appendf(output, "%s", addr_str) < 0)
|
||||
goto fail_unlock;
|
||||
}
|
||||
} else if (var->data.type == SMP_T_METH) {
|
||||
/* HTTP Method */
|
||||
if (var->data.u.meth.meth == HTTP_METH_OTHER) {
|
||||
if (chunk_escape_string(output, var->data.u.meth.str.area, var->data.u.meth.str.data) < 0)
|
||||
goto fail_unlock;
|
||||
} else {
|
||||
const char *method_str = http_known_methods[var->data.u.meth.meth].ptr;
|
||||
if (chunk_appendf(output, "\"%s\"", method_str) < 0)
|
||||
goto fail_unlock;
|
||||
}
|
||||
} else {
|
||||
/* Other types: show type number */
|
||||
if (chunk_appendf(output, "(type:%d)", var->data.type) < 0)
|
||||
goto fail_unlock;
|
||||
}
|
||||
|
||||
var = cebu64_item_next(&vars->name_root[i], name_node, name_hash, var);
|
||||
}
|
||||
}
|
||||
|
||||
vars_rdunlock(vars);
|
||||
|
||||
/* Set output sample */
|
||||
smp->data.type = SMP_T_STR;
|
||||
smp->data.u.str.area = output->area;
|
||||
smp->data.u.str.data = output->data;
|
||||
smp->flags &= ~SMP_F_CONST;
|
||||
|
||||
return 1;
|
||||
|
||||
fail_unlock:
|
||||
vars_rdunlock(vars);
|
||||
output->data = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear the contents of a variable so that it can be reset directly.
|
||||
* This function is used just before a variable is filled out of a sample's
|
||||
@ -916,6 +1091,60 @@ static int smp_check_var(struct arg *args, char **err)
|
||||
return vars_check_arg(&args[0], err);
|
||||
}
|
||||
|
||||
/* This function checks all arguments for dump_all_vars()
|
||||
* Args: [scope], [prefix], [delimiter]
|
||||
* Both arguments are optional
|
||||
*/
|
||||
static int smp_check_dump_all_vars(struct arg *args, char **err)
|
||||
{
|
||||
/* First argument (scope) is optional */
|
||||
|
||||
if (args[0].type == ARGT_STR) {
|
||||
const char *scope = args[0].data.str.area;
|
||||
int scope_id = -1;
|
||||
int i;
|
||||
char buf[16];
|
||||
|
||||
if (args[0].data.str.data < sizeof(buf) - 1) {
|
||||
snprintf(buf, sizeof(buf), "%s.", scope);
|
||||
|
||||
for (i = 0; i <= SCOPE_CHECK; i++) {
|
||||
if (strcmp(buf, var_scope_names[i]) == 0) {
|
||||
scope_id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scope_id == -1) {
|
||||
memprintf(err, "invalid scope '%s', must be one of: sess, txn, req, res, proc", scope);
|
||||
return 0;
|
||||
}
|
||||
|
||||
chunk_destroy(&args[0].data.str);
|
||||
args[0].type = ARGT_SINT;
|
||||
args[0].data.sint = scope_id;
|
||||
}
|
||||
else if (args[0].type != ARGT_STOP) {
|
||||
memprintf(err, "first argument must be a string (scope) or omitted");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Second argument (prefix) is optional */
|
||||
if (args[1].type != ARGT_STR && args[1].type != ARGT_STOP) {
|
||||
memprintf(err, "second argument must be a string (prefix) or omitted");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Third argument (delimiter) is optional */
|
||||
if (args[2].type != ARGT_STR && args[2].type != ARGT_STOP) {
|
||||
memprintf(err, "third argument must be a string (delimiter) or omitted");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int conv_check_var(struct arg *args, struct sample_conv *conv,
|
||||
const char *file, int line, char **err_msg)
|
||||
{
|
||||
@ -1410,6 +1639,7 @@ INITCALL0(STG_PREPARE, vars_init);
|
||||
|
||||
static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
|
||||
|
||||
{ "dump_all_vars", smp_fetch_dump_all_vars, ARG3(0,STR,STR,STR), smp_check_dump_all_vars, SMP_T_STR, SMP_USE_CONST },
|
||||
{ "var", smp_fetch_var, ARG2(1,STR,STR), smp_check_var, SMP_T_ANY, SMP_USE_CONST },
|
||||
{ /* END */ },
|
||||
}};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user