MINOR: sample: add the "when" converter to condition some expressions

Sometimes it would be desirable to include some debugging output only
under certain conditions, but the end of the transfer is too late to
apply some rules.

Here we take the approach of making a converter ("when") that takes a
condition among an arbitrary list, and decides whether or not to let
the input sample pass through or not based on the condition. This
allows for example to log debugging information only when an error
was encountered during the processing (sort of an extension of
dontlog-normal). The conditions are quite limited (stopping, error,
normal, toapplet, forwarded, processed) and can be negated. The
converter can also be chained to use more complex conditions.

A suggested example will be:

    # log "dbg={-}" when fine, or "dbg={... debug info ...}" on error:
    log-format "$HAPROXY_HTTP_LOG_FMT dbg={%[bs.debug_str,when(!normal)]}"
This commit is contained in:
Willy Tarreau 2024-10-22 19:43:18 +02:00
parent 19e4ec43b9
commit b74fb1325e
2 changed files with 181 additions and 0 deletions

View File

@ -19810,6 +19810,7 @@ url_enc([enc_type]) string string
us_ltime(format[,offset]) integer string us_ltime(format[,offset]) integer string
us_utime(format[,offset]) integer string us_utime(format[,offset]) integer string
utime(format[,offset]) integer string utime(format[,offset]) integer string
when(condition) any same
word(index,delimiters[,count]) string string word(index,delimiters[,count]) string string
wt6([avalanche]) binary integer wt6([avalanche]) binary integer
x509_v_err_str integer string x509_v_err_str integer string
@ -21397,6 +21398,67 @@ utime(<format>[,<offset>])
# e.g. 20140710162350 127.0.0.1:57325 # e.g. 20140710162350 127.0.0.1:57325
log-format %[date,utime(%Y%m%d%H%M%S)]\ %ci:%cp log-format %[date,utime(%Y%m%d%H%M%S)]\ %ci:%cp
when(<condition>)
Evaluates the condition and when true, passes the input sample as-is to the
output, otherwise return nothing. This is designed specifically to produce
some rarely needed data that should only be emitted under certain conditions,
such as debugging information when an error is met.
The condition is made of a keyword among the list below, optionally preceeded
by an exclamation mark ('!') to negate it:
- "error" returns true when an error was encountered during the processing
of the request or stream. It uses the same rules as "dontlog-normal"
(e.g. a successful redispatch counts as an error).
- "forwarded" returns true when the request was forwarded to a backend
server
- "normal" returns true when no error happened (this is equivalent to
"!error").
- "processed" returns true when the request was either forwarded to a
backend server, or processed by an applet.
- "stopping" returns true if the process is currently stopping when the
rule is evaluated
- "toapplet" returns true when the request was processed by an applet.
Note that the content is evaluated in any case, so doing this does not avoid
the generation of that information. It's only meant to avoid producing that
information.
An example would be to add backend stream debugging information in the logs
only when an error was encountered during processing, or logging extra
information when stopping, etc.
Example:
# log "dbg={-}" when fine, or "dbg={... debug info ...}" on error:
log-format "$HAPROXY_HTTP_LOG_FMT dbg={%[bs.debug_str,when(!normal)]}"
Here, the "dbg" field in the log will only contain an dash ('-') to
indicate a missing content when the rule is not validated, and will emit a
whole debugging block when it is.
Example
# only emit the backend src/port when a real connection was issued:
log-format "$HAPROXY_HTTP_LOG_FMT \
src=[%[bc_src,when(forwarded)]:%[bc_src_port,when(forwarded)]]"
Since it kills the evaluation of the expression when it is not true, it is
also possible to use it to stop a subsequent converter from being called.
This may for example be used to call the debug() converter only upon error,
to log an element only when absolutely necessary.
Example:
# emit the whole response headers list to stderr only on error and only
# when the output is a connection. We abuse a dummy variable here.
http-after-response set-var(res.test) \
res.hdrs,when(error),when(forwarded),debug(hdrs,stderr)
See also: debug converter
word(<index>,<delimiters>[,<count>]) word(<index>,<delimiters>[,<count>])
Extracts the nth word counting from the beginning (positive index) or from Extracts the nth word counting from the beginning (positive index) or from
the end (negative index) considering given delimiters from an input string. the end (negative index) considering given delimiters from an input string.

View File

@ -31,6 +31,7 @@
#include <haproxy/global.h> #include <haproxy/global.h>
#include <haproxy/hash.h> #include <haproxy/hash.h>
#include <haproxy/http.h> #include <haproxy/http.h>
#include <haproxy/http_ana-t.h>
#include <haproxy/istbuf.h> #include <haproxy/istbuf.h>
#include <haproxy/mqtt.h> #include <haproxy/mqtt.h>
#include <haproxy/net_helper.h> #include <haproxy/net_helper.h>
@ -38,6 +39,7 @@
#include <haproxy/proxy.h> #include <haproxy/proxy.h>
#include <haproxy/regex.h> #include <haproxy/regex.h>
#include <haproxy/sample.h> #include <haproxy/sample.h>
#include <haproxy/sc_strm.h>
#include <haproxy/sink.h> #include <haproxy/sink.h>
#include <haproxy/stick_table.h> #include <haproxy/stick_table.h>
#include <haproxy/time.h> #include <haproxy/time.h>
@ -3816,6 +3818,122 @@ static int sample_conv_iif(const struct arg *arg_p, struct sample *smp, void *pr
return 1; return 1;
} }
enum {
WHEN_COND_STOPPING,
WHEN_COND_NORMAL,
WHEN_COND_ERROR,
WHEN_COND_FORWARDED,
WHEN_COND_TOAPPLET,
WHEN_COND_PROCESSED,
WHEN_COND_CONDITIONS
};
const char *when_cond_kw[WHEN_COND_CONDITIONS] = {
[WHEN_COND_STOPPING] = "stopping",
[WHEN_COND_NORMAL] = "normal",
[WHEN_COND_ERROR] = "error",
[WHEN_COND_FORWARDED] = "forwarded",
[WHEN_COND_TOAPPLET] = "toapplet",
[WHEN_COND_PROCESSED] = "processed",
};
/* Evaluates a condition and decides whether or not to pass the input sample
* to the output. The purpose is to hide some info when certain conditions are
* (not) met. These conditions belong to a fixed list that can verify internal
* states (debug mode, too high load, reloading, server down, stream in error
* etc). The condition's sign is placed in arg_p[0].data.int. 0=direct, 1=inv.
* The condition keyword is in arg_p[1].data.int (WHEN_COND_*).
*/
static int sample_conv_when(const struct arg *arg_p, struct sample *smp, void *private)
{
struct session *sess = smp->sess;
struct stream *strm = smp->strm;
int neg = arg_p[0].data.sint;
int cond = arg_p[1].data.sint;
int ret = 0;
switch (cond) {
case WHEN_COND_STOPPING:
ret = !!stopping;
break;
case WHEN_COND_NORMAL:
neg = !neg;
__fallthrough;
case WHEN_COND_ERROR:
if (strm &&
((strm->flags & SF_REDISP) ||
((strm->flags & SF_ERR_MASK) > SF_ERR_LOCAL) ||
(((strm->flags & SF_ERR_MASK) == SF_ERR_NONE) && strm->conn_retries) ||
((sess->fe->mode == PR_MODE_HTTP) && strm->txn && strm->txn->status >= 500)))
ret = 1;
break;
case WHEN_COND_FORWARDED: // true if forwarded to a connection
ret = !!sc_conn(smp->strm->scb);
break;
case WHEN_COND_TOAPPLET: // true if handled as an applet
ret = !!sc_appctx(smp->strm->scb);
break;
case WHEN_COND_PROCESSED: // true if forwarded or appctx
ret = sc_conn(smp->strm->scb) || sc_appctx(smp->strm->scb);
break;
}
ret = !!ret ^ !!neg;
if (!ret) {
/* kill the sample */
return 0;
}
/* pass the sample as-is */
return 1;
}
/* checks and resolves the type of the argument passed to when().
* It supports an optional '!' to negate the condition, followed by
* a keyword among the list above.
*/
static int check_when_cond(struct arg *args, struct sample_conv *conv,
const char *file, int line, char **err)
{
const char *kw;
int neg = 0;
int i;
kw = args[0].data.str.area;
if (*kw == '!') {
kw++;
neg = 1;
}
for (i = 0; i < WHEN_COND_CONDITIONS; i++) {
if (strcmp(kw, when_cond_kw[i]) == 0)
break;
}
if (i == WHEN_COND_CONDITIONS) {
memprintf(err, "expects a supported keyword among {");
for (i = 0; i < WHEN_COND_CONDITIONS; i++)
memprintf(err, "%s%s%s", *err, when_cond_kw[i], (i == WHEN_COND_CONDITIONS - 1) ? "}" : ",");
memprintf(err, "%s but got '%s'", *err, kw);
return 0;
}
chunk_destroy(&args[0].data.str);
// store condition
args[0].type = ARGT_SINT;
args[0].data.sint = neg; // '!' present
// and keyword
args[1].type = ARGT_SINT;
args[1].data.sint = i;
return 1;
}
#define GRPC_MSG_COMPRESS_FLAG_SZ 1 /* 1 byte */ #define GRPC_MSG_COMPRESS_FLAG_SZ 1 /* 1 byte */
#define GRPC_MSG_LENGTH_SZ 4 /* 4 bytes */ #define GRPC_MSG_LENGTH_SZ 4 /* 4 bytes */
#define GRPC_MSG_HEADER_SZ (GRPC_MSG_COMPRESS_FLAG_SZ + GRPC_MSG_LENGTH_SZ) #define GRPC_MSG_HEADER_SZ (GRPC_MSG_COMPRESS_FLAG_SZ + GRPC_MSG_LENGTH_SZ)
@ -5268,6 +5386,7 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, {
{ "jwt_payload_query", sample_conv_jwt_payload_query, ARG2(0,STR,STR), sample_conv_jwt_query_check, SMP_T_BIN, SMP_T_ANY }, { "jwt_payload_query", sample_conv_jwt_payload_query, ARG2(0,STR,STR), sample_conv_jwt_query_check, SMP_T_BIN, SMP_T_ANY },
{ "jwt_verify", sample_conv_jwt_verify, ARG2(2,STR,STR), sample_conv_jwt_verify_check, SMP_T_BIN, SMP_T_SINT }, { "jwt_verify", sample_conv_jwt_verify, ARG2(2,STR,STR), sample_conv_jwt_verify_check, SMP_T_BIN, SMP_T_SINT },
#endif #endif
{ "when", sample_conv_when, ARG1(1,STR), check_when_cond, SMP_T_ANY, SMP_T_ANY },
{ NULL, NULL, 0, 0, 0 }, { NULL, NULL, 0, 0, 0 },
}}; }};