---
Changes based on feedback V2 (20260113):
- Applied goto based error handling logic in duplicated block.
- Use dump_binary to convert bin to hex
- Removed double-quotes in IP addr
- Modified to loop var_scope_names to search and compare strings
- Fixed typo (delimieter => delimiter)
- Detached chunk_escape_string function to dedicated commit (before this commit)
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.
---
src/vars.c | 231 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 231 insertions(+)
diff --git a/src/vars.c b/src/vars.c
index 2654155ce..68024553a 100644
--- a/src/vars.c
+++ b/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,172 @@ 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 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;
+
+ struct buffer *output;
+
+ /* 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 +1092,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 +1640,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 */ },
}};
--
2.48.1