Skip to content

Commit

Permalink
MINOR: sample: add the "when" converter to condition some expressions
Browse files Browse the repository at this point in the history
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)]}"
  • Loading branch information
wtarreau committed Oct 22, 2024
1 parent 19e4ec4 commit b74fb13
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 0 deletions.
62 changes: 62 additions & 0 deletions doc/configuration.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -21397,6 +21398,67 @@ utime(<format>[,<offset>])
# e.g. 20140710162350 127.0.0.1:57325
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>])
Extracts the nth word counting from the beginning (positive index) or from
the end (negative index) considering given delimiters from an input string.
Expand Down
119 changes: 119 additions & 0 deletions src/sample.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@
#include <haproxy/global.h>
#include <haproxy/hash.h>
#include <haproxy/http.h>
#include <haproxy/http_ana-t.h>
#include <haproxy/istbuf.h>
#include <haproxy/mqtt.h>
#include <haproxy/net_helper.h>
#include <haproxy/protobuf.h>
#include <haproxy/proxy.h>
#include <haproxy/regex.h>
#include <haproxy/sample.h>
#include <haproxy/sc_strm.h>
#include <haproxy/sink.h>
#include <haproxy/stick_table.h>
#include <haproxy/time.h>
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 },
}};

Expand Down

0 comments on commit b74fb13

Please sign in to comment.