MEDIUM: checks: Add on-error/on-success option on tcp-check expect rules

These options define log-format strings used to produce the info message if a
tcp-check expect rule fails (on-error option) or succeeds (on-success
option). For this last option, it must be the ending rule, otherwise the
parameter is ignored.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index b253ca9..ee1ef18 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -9873,6 +9873,7 @@
 
 
 tcp-check expect [min-recv <int>] [error-status <st>] [tout-status <st>]
+                 [on-success <fmt>] [on-error <fmt>]
                  [!] <match> <pattern>
   Specify data to be collected and analyzed during a generic health check
   May be used in sections:   defaults | frontend | listen | backend
@@ -9911,6 +9912,16 @@
                        HCHK_STATUS_L6TOUT or HCHK_STATUS_L4TOUT timeout status.
                        By default "L7TOUT" is used.
 
+    on-success <fmt>   is optional and can be used to customize the
+                       informational message reported in logs if the expect
+                       rule is successfully evaluated and if it is the last rule
+                       in the tcp-check ruleset. <fmt> is a log-format string.
+
+    on-error <fmt>     is optional and can be used to customize the
+                       informational message reported in logs if an error
+                       occurred during the expect rule evaluation. <fmt> is a
+                       log-format string.
+
     <pattern> is the pattern to look for. It may be a string or a regular
               expression. If the pattern contains spaces, they must be escaped
               with the usual backslash ('\').
diff --git a/include/types/checks.h b/include/types/checks.h
index ca1333f..0d479e3 100644
--- a/include/types/checks.h
+++ b/include/types/checks.h
@@ -264,6 +264,8 @@
 	int inverse;                    /* Match is inversed. */
 	int with_capture;               /* Match will store captured groups for back-reference in comment. */
 	int min_recv;                   /* Minimum amount of data before an expect can be applied. (default: -1, ignored) */
+	struct list onerror_fmt;        /* log-format string to use as comment on error */
+	struct list onsuccess_fmt;      /* log-format string to use as comment on success (if last rule) */
 	enum healthcheck_status err_status;  /* The healthcheck status to use on error (default: L7RSP) */
 	enum healthcheck_status tout_status; /* The healthcheck status to use on timeout (default: L7TOUT) */
 };
diff --git a/src/checks.c b/src/checks.c
index 3606aa0..01fc2c3 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -3073,7 +3073,7 @@
 {
 	enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
 	struct tcpcheck_expect *expect = &check->current_step->expect;
-	char *diag;
+	struct buffer *msg = NULL;
 	int match;
 
 	/* The current expect might need more data than the previous one, check again
@@ -3138,24 +3138,23 @@
 
 	/* From this point on, we matched something we did not want, this is an error state. */
 	ret = TCPCHK_EVAL_STOP;
+	msg = alloc_trash_chunk();
+	if (!msg)
+		goto no_desc;
 
-	diag = match ? "matched unwanted content" : "did not match content";
+	chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
 	switch (expect->type) {
 	case TCPCHK_EXPECT_STRING:
-		chunk_printf(&trash, "TCPCHK %s '%s' at step %d",
-			     diag, expect->string, tcpcheck_get_step_id(check, rule));
+		chunk_appendf(msg, " '%s' at step %d", expect->string, tcpcheck_get_step_id(check, rule));
 		break;
 	case TCPCHK_EXPECT_BINARY:
-		chunk_printf(&trash, "TCPCHK %s (binary) at step %d",
-			     diag, tcpcheck_get_step_id(check, rule));
+		chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
 		break;
 	case TCPCHK_EXPECT_REGEX:
-		chunk_printf(&trash, "TCPCHK %s (regex) at step %d",
-			     diag, tcpcheck_get_step_id(check, rule));
+		chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
 		break;
 	case TCPCHK_EXPECT_REGEX_BINARY:
-		chunk_printf(&trash, "TCPCHK %s (binary regex) at step %d",
-			     diag, tcpcheck_get_step_id(check, rule));
+		chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
 
 		/* If references to the matched text were made, divide the
 		 * offsets by 2 to match offset of the original response buffer.
@@ -3171,22 +3170,29 @@
 		break;
 	case TCPCHK_EXPECT_UNDEF:
 		/* Should never happen. */
-		goto out;
+		goto no_desc;
 	}
 
-	if (rule->comment) {
+	if (!LIST_ISEMPTY(&expect->onerror_fmt)) {
+		chunk_strcat(msg, " comment: ");
+		msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &expect->onerror_fmt);
+	}
+	else if (rule->comment) {
 		if (expect->with_capture) {
-			ret = exp_replace(b_tail(&trash), b_room(&trash), b_head(&check->bi), rule->comment, pmatch);
-			if (ret > 0) /* ignore comment if too large */
-				trash.data += ret;
+			ret = exp_replace(b_tail(msg), b_room(msg), b_head(&check->bi), rule->comment, pmatch);
+			if (ret != -1) /* ignore comment if too large */
+				msg->data += ret;
 		}
 		else
-			chunk_appendf(&trash, " comment: '%s'", rule->comment);
+			chunk_appendf(msg, " comment: '%s'", rule->comment);
 	}
-	set_server_check_status(check, expect->err_status, trash.area);
+
+  no_desc:
+	set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
 	ret = TCPCHK_EVAL_STOP;
 
   out:
+	free_trash_chunk(msg);
 	return ret;
 }
 
@@ -3224,6 +3230,7 @@
 	struct tcpcheck_rule *rule;
 	struct conn_stream *cs = check->cs;
 	struct connection *conn = cs_conn(cs);
+	struct buffer *msg = NULL;
 	int must_read = 1, last_read = 0;
 	int ret, retcode = 0;
 
@@ -3379,7 +3386,17 @@
 	}
 
 	/* All rules was evaluated */
-	set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
+	if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT &&
+	    !LIST_ISEMPTY(&check->current_step->expect.onsuccess_fmt)) {
+		msg = alloc_trash_chunk();
+		if (msg)
+			msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
+							&check->current_step->expect.onsuccess_fmt);
+	}
+
+	set_server_check_status(check, HCHK_STATUS_L7OKD, (msg ? b_head(msg) : "(tcp-check)"));
+	check->current_step = NULL;
+	free_trash_chunk(msg);
 
   out_end_tcpcheck:
 	if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
@@ -3463,6 +3480,18 @@
 		}
 		break;
 	case TCPCHK_ACT_EXPECT:
+		list_for_each_entry_safe(lf, lfb, &rule->expect.onerror_fmt, list) {
+			LIST_DEL(&lf->list);
+			release_sample_expr(lf->expr);
+			free(lf->arg);
+			free(lf);
+		}
+		list_for_each_entry_safe(lf, lfb, &rule->expect.onsuccess_fmt, list) {
+			LIST_DEL(&lf->list);
+			release_sample_expr(lf->expr);
+			free(lf->arg);
+			free(lf);
+		}
 		switch (rule->expect.type) {
 		case TCPCHK_EXPECT_STRING:
 		case TCPCHK_EXPECT_BINARY:
@@ -3636,6 +3665,8 @@
 
 	expect = &tcpcheck->expect;
 	expect->type = TCPCHK_EXPECT_STRING;
+	LIST_INIT(&expect->onerror_fmt);
+	LIST_INIT(&expect->onsuccess_fmt);
 	expect->err_status = HCHK_STATUS_L7RSP;
 	expect->tout_status = HCHK_STATUS_L7TOUT;
 	expect->string = strdup(str);
@@ -4525,13 +4556,14 @@
 						   const char *file, int line, char **errmsg)
 {
 	struct tcpcheck_rule *prev_check, *chk = NULL;
-	char *str = NULL, *comment = NULL, *pattern = NULL;
+	char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
 	enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
 	enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
 	enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
 	long min_recv = -1;
 	int inverse = 0, with_capture = 0;
 
+	str = on_success_msg = on_error_msg = comment = pattern = NULL;
 	if (!*(args[cur_arg+1]) || !*(args[cur_arg+2])) {
 		memprintf(errmsg, "expects a pattern (type+string) as arguments");
 		goto error;
@@ -4603,6 +4635,40 @@
 				goto error;
 			}
 		}
+		else if (strcmp(args[cur_arg], "on-success") == 0) {
+			if (in_pattern) {
+				memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
+				goto error;
+			}
+			if (!*(args[cur_arg+1])) {
+				memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
+				goto error;
+			}
+			cur_arg++;
+			free(on_success_msg);
+			on_success_msg = strdup(args[cur_arg]);
+			if (!on_success_msg) {
+				memprintf(errmsg, "out of memory");
+				goto error;
+			}
+		}
+		else if (strcmp(args[cur_arg], "on-error") == 0) {
+			if (in_pattern) {
+				memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
+				goto error;
+			}
+			if (!*(args[cur_arg+1])) {
+				memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
+				goto error;
+			}
+			cur_arg++;
+			free(on_error_msg);
+			on_error_msg = strdup(args[cur_arg]);
+			if (!on_error_msg) {
+				memprintf(errmsg, "out of memory");
+				goto error;
+			}
+		}
 		else if (strcmp(args[cur_arg], "error-status") == 0) {
 			if (in_pattern) {
 				memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
@@ -4683,7 +4749,9 @@
 		goto error;
 	}
 	chk->action  = TCPCHK_ACT_EXPECT;
-	chk->comment = comment;
+	LIST_INIT(&chk->expect.onerror_fmt);
+	LIST_INIT(&chk->expect.onsuccess_fmt);
+	chk->comment = comment; comment = NULL;
 	chk->expect.type = type;
 	chk->expect.min_recv = min_recv;
 	chk->expect.inverse = inverse;
@@ -4691,6 +4759,25 @@
 	chk->expect.err_status = err_st;
 	chk->expect.tout_status = tout_st;
 
+	if (on_success_msg) {
+		px->conf.args.ctx = ARGC_SRV;
+		if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
+			memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
+			goto error;
+		}
+		free(on_success_msg);
+		on_success_msg = NULL;
+	}
+	if (on_error_msg) {
+		px->conf.args.ctx = ARGC_SRV;
+		if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
+			memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
+			goto error;
+		}
+		free(on_error_msg);
+		on_error_msg = NULL;
+	}
+
 	switch (chk->expect.type) {
 	case TCPCHK_EXPECT_STRING:
 		chk->expect.string = strdup(pattern);
@@ -4733,9 +4820,11 @@
 	return chk;
 
   error:
-	free(chk);
+	free_tcpcheck(chk, 0);
 	free(str);
 	free(comment);
+	free(on_success_msg);
+	free(on_error_msg);
 	return NULL;
 }