From b74fb1325e60a2573f1ed8a460129bc6c0c9e53f Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Tue, 22 Oct 2024 19:43:18 +0200 Subject: [PATCH] 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)]}" --- doc/configuration.txt | 62 ++++++++++++++++++++++ src/sample.c | 119 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) diff --git a/doc/configuration.txt b/doc/configuration.txt index 5e619a7fe87f6..3a63798bfda13 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -19810,6 +19810,7 @@ url_enc([enc_type]) string string us_ltime(format[,offset]) integer string us_utime(format[,offset]) integer string utime(format[,offset]) integer string +when(condition) any same word(index,delimiters[,count]) string string wt6([avalanche]) binary integer x509_v_err_str integer string @@ -21397,6 +21398,67 @@ utime([,]) # e.g. 20140710162350 127.0.0.1:57325 log-format %[date,utime(%Y%m%d%H%M%S)]\ %ci:%cp +when() + 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(,[,]) Extracts the nth word counting from the beginning (positive index) or from the end (negative index) considering given delimiters from an input string. diff --git a/src/sample.c b/src/sample.c index 844b273d6d0b3..52760d916eb2d 100644 --- a/src/sample.c +++ b/src/sample.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -3816,6 +3818,122 @@ static int sample_conv_iif(const struct arg *arg_p, struct sample *smp, void *pr 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_LENGTH_SZ 4 /* 4 bytes */ #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_verify", sample_conv_jwt_verify, ARG2(2,STR,STR), sample_conv_jwt_verify_check, SMP_T_BIN, SMP_T_SINT }, #endif + { "when", sample_conv_when, ARG1(1,STR), check_when_cond, SMP_T_ANY, SMP_T_ANY }, { NULL, NULL, 0, 0, 0 }, }};