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 | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 184 insertions(+)
diff --git a/src/vars.c b/src/vars.c
index a11101946..f2d819035 100644
--- a/src/vars.c
+++ b/src/vars.c
@@ -355,6 +355,152 @@ 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.
+ */
+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;
+ int first = 1;
+ int i;
+
+ /* Allocate output buffer */
+ struct buffer *output = alloc_trash_chunk();
+ if (!output)
+ return 0;
+
+ chunk_reset(output);
+
+ /* Parse arguments */
+ if (args[0].type == ARGT_STR) {
+ const char *scope_str = args[0].data.str.area;
+ size_t scope_len = args[0].data.str.data;
+
+ if (scope_len == 4 && strncmp(scope_str, "sess", 4) == 0)
+ desc.scope = SCOPE_SESS;
+ else if (scope_len == 3 && strncmp(scope_str, "txn", 3) == 0)
+ desc.scope = SCOPE_TXN;
+ else if (scope_len == 3 && strncmp(scope_str, "req", 3) == 0)
+ desc.scope = SCOPE_REQ;
+ else if (scope_len == 3 && strncmp(scope_str, "res", 3) == 0)
+ desc.scope = SCOPE_RES;
+ else if (scope_len == 4 && strncmp(scope_str, "proc", 4) == 0)
+ desc.scope = SCOPE_PROC;
+ else {
+ free_trash_chunk(output);
+ return 0;
+ }
+
+ if (args[1].type == ARGT_STR) {
+ prefix = args[1].data.str.area;
+ prefix_len = args[1].data.str.data;
+ }
+ } else {
+ if (smp->strm)
+ desc.scope = SCOPE_TXN;
+ else if (smp->sess)
+ desc.scope = SCOPE_SESS;
+ else
+ desc.scope = SCOPE_PROC;
+ }
+
+ desc.flags = 0;
+ desc.name_hash = 0;
+
+ vars = get_vars(smp->sess, smp->strm, &desc);
+ if (!vars || vars->scope != desc.scope) {
+ free_trash_chunk(output);
+ 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) {
+ /* Check prefix filter */
+ if (prefix && var->name) {
+ if (strncmp(var->name, prefix, prefix_len) !=
0) {
+ var =
cebu64_item_next(&vars->name_root[i], name_node, name_hash, var);
+ continue;
+ }
+ }
+
+ /* Add comma separator */
+ if (!first) {
+ chunk_appendf(output, ", ");
+ }
+ first = 0;
+
+ /* Add variable name with scope prefix */
+ if (var->name) {
+ const char *scope_prefix = "";
+ switch (desc.scope) {
+ case SCOPE_SESS: scope_prefix =
"sess."; break;
+ case SCOPE_TXN: scope_prefix = "txn.";
break;
+ case SCOPE_REQ: scope_prefix = "req.";
break;
+ case SCOPE_RES: scope_prefix = "res.";
break;
+ case SCOPE_PROC: scope_prefix =
"proc."; break;
+ default: break;
+ }
+ chunk_appendf(output, "%s%s=", scope_prefix,
var->name);
+ } else {
+ chunk_appendf(output, "var_%016llx=", (unsigned
long long)var->name_hash);
+ }
+
+ /* Convert value based on type - WITHOUT
sample_convert() */
+ if (var->data.type == SMP_T_STR || var->data.type ==
SMP_T_BIN) {
+ /* String: quote and escape */
+ chunk_appendf(output, "\"");
+ const char *str = var->data.u.str.area;
+ size_t len = var->data.u.str.data;
+
+ for (size_t j = 0; j < len && output->data <
output->size - 10; j++) {
+ unsigned char c = str[j];
+ if (c == '"') chunk_appendf(output,
"\\\"");
+ else if (c == '\\')
chunk_appendf(output, "\\\\");
+ else if (c == '\r')
chunk_appendf(output, "\\r");
+ else if (c == '\n')
chunk_appendf(output, "\\n");
+ else if (c == '\b')
chunk_appendf(output, "\\b");
+ else if (c == '\0')
chunk_appendf(output, "\\0");
+ else chunk_appendf(output, "%c", c);
+ }
+ chunk_appendf(output, "\"");
+
+ } else if (var->data.type == SMP_T_SINT) {
+ /* Integer */
+ chunk_appendf(output, "%lld", (long
long)var->data.u.sint);
+
+ } else if (var->data.type == SMP_T_BOOL) {
+ /* Boolean */
+ chunk_appendf(output, "%s", var->data.u.sint ?
"true" : "false");
+
+ } else {
+ /* Other types: unconvertible */
+ chunk_appendf(output, "(type:%d)",
var->data.type);
+ }
+
+ 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 = *output;
+ smp->flags = SMP_F_VOL_TEST | SMP_F_CONST;
+
+ return 1;
+}
+
/*
* 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
@@ -915,6 +1061,43 @@ 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]
+ * Both arguments are optional strings
+ */
+static int smp_check_dump_all_vars(struct arg *args, char **err)
+{
+ /* First argument (scope) is optional */
+ if (args[0].type != ARGT_STR && args[0].type != ARGT_STOP) {
+ memprintf(err, "first argument must be a string (scope) or
omitted");
+ return 0;
+ }
+
+ /* If scope is provided, validate it */
+ if (args[0].type == ARGT_STR) {
+ const char *scope = args[0].data.str.area;
+ size_t len = args[0].data.str.data;
+
+ if (!((len == 4 && strncmp(scope, "sess", 4) == 0) ||
+ (len == 3 && strncmp(scope, "txn", 3) == 0) ||
+ (len == 3 && strncmp(scope, "req", 3) == 0) ||
+ (len == 3 && strncmp(scope, "res", 3) == 0) ||
+ (len == 4 && strncmp(scope, "proc", 4) == 0))) {
+ memprintf(err, "invalid scope '%.*s', must be one of:
sess, txn, req, res, proc",
+ (int)len, scope);
+ 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;
+ }
+
+ return 1;
+}
+
static int conv_check_var(struct arg *args, struct sample_conv *conv,
const char *file, int line, char **err_msg)
{
@@ -1409,6 +1592,7 @@ INITCALL0(STG_PREPARE, vars_init);
static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
{ "var", smp_fetch_var, ARG2(1,STR,STR), smp_check_var, SMP_T_ANY,
SMP_USE_CONST },
+ { "dump_all_vars", smp_fetch_dump_all_vars, ARG2(0,STR,STR),
smp_check_dump_all_vars, SMP_T_STR, SMP_USE_CONST },
{ /* END */ },
}};
--
2.50.1 (Apple Git-155)