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 }, }};