diff --git a/include/types/cli.h b/include/types/cli.h index ebe4adb62..0ebfc69b4 100644 --- a/include/types/cli.h +++ b/include/types/cli.h @@ -47,7 +47,6 @@ enum { STAT_CLI_PROMPT, /* display the prompt (first output, same code) */ STAT_CLI_PRINT, /* display message in cli->msg */ STAT_CLI_PRINT_FREE, /* display message in cli->msg. After the display, free the pointer */ - STAT_CLI_O_ERR, /* dump errors */ STAT_CLI_O_ENV, /* dump environment */ STAT_CLI_O_CUSTOM, /* custom callback pointer */ }; diff --git a/src/cli.c b/src/cli.c index 39fe01fde..b1d2e54df 100644 --- a/src/cli.c +++ b/src/cli.c @@ -56,7 +56,6 @@ #include #include #include -#include #include #include #include @@ -68,7 +67,6 @@ #include static int stats_dump_env_to_buffer(struct stream_interface *si); -static int stats_dump_errors_to_buffer(struct stream_interface *si); static struct applet cli_applet; @@ -79,7 +77,6 @@ static const char stats_sock_usage_msg[] = " prompt : toggle interactive mode with prompt\n" " quit : disconnect\n" " show env [var] : dump environment variables known to the process\n" - " show errors : report last request and response errors for each proxy\n" " set timeout : change a timeout setting\n" " set maxconn : change a maxconn setting\n" " set rate-limit : change a rate limiting value\n" @@ -577,20 +574,6 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) appctx->st2 = STAT_ST_END; } } - else if (strcmp(args[1], "errors") == 0) { - if (strm_li(s)->bind_conf->level < ACCESS_LVL_OPER) { - appctx->ctx.cli.msg = stats_permission_denied_msg; - appctx->st0 = STAT_CLI_PRINT; - return 1; - } - if (*args[2]) - appctx->ctx.errors.iid = atoi(args[2]); - else - appctx->ctx.errors.iid = -1; - appctx->ctx.errors.px = NULL; - appctx->st2 = STAT_ST_INIT; - appctx->st0 = STAT_CLI_O_ERR; // stats_dump_errors_to_buffer - } else { /* neither "stat" nor "info" nor "sess" nor "errors" nor "table" */ return 0; } @@ -1291,10 +1274,6 @@ static void cli_io_handler(struct appctx *appctx) else si_applet_cant_put(si); break; - case STAT_CLI_O_ERR: /* errors dump */ - if (stats_dump_errors_to_buffer(si)) - appctx->st0 = STAT_CLI_PROMPT; - break; case STAT_CLI_O_ENV: /* environment dump */ if (stats_dump_env_to_buffer(si)) appctx->st0 = STAT_CLI_PROMPT; @@ -1385,169 +1364,6 @@ static void cli_release_handler(struct appctx *appctx) } } -/* This function dumps all captured errors onto the stream interface's - * read buffer. It returns 0 if the output buffer is full and it needs - * to be called again, otherwise non-zero. - */ -static int stats_dump_errors_to_buffer(struct stream_interface *si) -{ - struct appctx *appctx = __objt_appctx(si->end); - extern const char *monthname[12]; - - if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW))) - return 1; - - chunk_reset(&trash); - - if (!appctx->ctx.errors.px) { - /* the function had not been called yet, let's prepare the - * buffer for a response. - */ - struct tm tm; - - get_localtime(date.tv_sec, &tm); - chunk_appendf(&trash, "Total events captured on [%02d/%s/%04d:%02d:%02d:%02d.%03d] : %u\n", - tm.tm_mday, monthname[tm.tm_mon], tm.tm_year+1900, - tm.tm_hour, tm.tm_min, tm.tm_sec, (int)(date.tv_usec/1000), - error_snapshot_id); - - if (bi_putchk(si_ic(si), &trash) == -1) { - /* Socket buffer full. Let's try again later from the same point */ - si_applet_cant_put(si); - return 0; - } - - appctx->ctx.errors.px = proxy; - appctx->ctx.errors.buf = 0; - appctx->ctx.errors.bol = 0; - appctx->ctx.errors.ptr = -1; - } - - /* we have two inner loops here, one for the proxy, the other one for - * the buffer. - */ - while (appctx->ctx.errors.px) { - struct error_snapshot *es; - - if (appctx->ctx.errors.buf == 0) - es = &appctx->ctx.errors.px->invalid_req; - else - es = &appctx->ctx.errors.px->invalid_rep; - - if (!es->when.tv_sec) - goto next; - - if (appctx->ctx.errors.iid >= 0 && - appctx->ctx.errors.px->uuid != appctx->ctx.errors.iid && - es->oe->uuid != appctx->ctx.errors.iid) - goto next; - - if (appctx->ctx.errors.ptr < 0) { - /* just print headers now */ - - char pn[INET6_ADDRSTRLEN]; - struct tm tm; - int port; - - get_localtime(es->when.tv_sec, &tm); - chunk_appendf(&trash, " \n[%02d/%s/%04d:%02d:%02d:%02d.%03d]", - tm.tm_mday, monthname[tm.tm_mon], tm.tm_year+1900, - tm.tm_hour, tm.tm_min, tm.tm_sec, (int)(es->when.tv_usec/1000)); - - switch (addr_to_str(&es->src, pn, sizeof(pn))) { - case AF_INET: - case AF_INET6: - port = get_host_port(&es->src); - break; - default: - port = 0; - } - - switch (appctx->ctx.errors.buf) { - case 0: - chunk_appendf(&trash, - " frontend %s (#%d): invalid request\n" - " backend %s (#%d)", - appctx->ctx.errors.px->id, appctx->ctx.errors.px->uuid, - (es->oe->cap & PR_CAP_BE) ? es->oe->id : "", - (es->oe->cap & PR_CAP_BE) ? es->oe->uuid : -1); - break; - case 1: - chunk_appendf(&trash, - " backend %s (#%d): invalid response\n" - " frontend %s (#%d)", - appctx->ctx.errors.px->id, appctx->ctx.errors.px->uuid, - es->oe->id, es->oe->uuid); - break; - } - - chunk_appendf(&trash, - ", server %s (#%d), event #%u\n" - " src %s:%d, session #%d, session flags 0x%08x\n" - " HTTP msg state %d, msg flags 0x%08x, tx flags 0x%08x\n" - " HTTP chunk len %lld bytes, HTTP body len %lld bytes\n" - " buffer flags 0x%08x, out %d bytes, total %lld bytes\n" - " pending %d bytes, wrapping at %d, error at position %d:\n \n", - es->srv ? es->srv->id : "", es->srv ? es->srv->puid : -1, - es->ev_id, - pn, port, es->sid, es->s_flags, - es->state, es->m_flags, es->t_flags, - es->m_clen, es->m_blen, - es->b_flags, es->b_out, es->b_tot, - es->len, es->b_wrap, es->pos); - - if (bi_putchk(si_ic(si), &trash) == -1) { - /* Socket buffer full. Let's try again later from the same point */ - si_applet_cant_put(si); - return 0; - } - appctx->ctx.errors.ptr = 0; - appctx->ctx.errors.sid = es->sid; - } - - if (appctx->ctx.errors.sid != es->sid) { - /* the snapshot changed while we were dumping it */ - chunk_appendf(&trash, - " WARNING! update detected on this snapshot, dump interrupted. Please re-check!\n"); - if (bi_putchk(si_ic(si), &trash) == -1) { - si_applet_cant_put(si); - return 0; - } - goto next; - } - - /* OK, ptr >= 0, so we have to dump the current line */ - while (es->buf && appctx->ctx.errors.ptr < es->len && appctx->ctx.errors.ptr < global.tune.bufsize) { - int newptr; - int newline; - - newline = appctx->ctx.errors.bol; - newptr = dump_text_line(&trash, es->buf, global.tune.bufsize, es->len, &newline, appctx->ctx.errors.ptr); - if (newptr == appctx->ctx.errors.ptr) - return 0; - - if (bi_putchk(si_ic(si), &trash) == -1) { - /* Socket buffer full. Let's try again later from the same point */ - si_applet_cant_put(si); - return 0; - } - appctx->ctx.errors.ptr = newptr; - appctx->ctx.errors.bol = newline; - }; - next: - appctx->ctx.errors.bol = 0; - appctx->ctx.errors.ptr = -1; - appctx->ctx.errors.buf++; - if (appctx->ctx.errors.buf > 1) { - appctx->ctx.errors.buf = 0; - appctx->ctx.errors.px = appctx->ctx.errors.px->next; - } - } - - /* dump complete */ - return 1; -} - /* This function dumps all environmnent variables to the buffer. It returns 0 * if the output buffer is full and it needs to be called again, otherwise * non-zero. Dumps only one entry if st2 == STAT_ST_END. diff --git a/src/proto_http.c b/src/proto_http.c index 181dfc2bf..a792fcf41 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -50,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -12823,6 +12825,196 @@ struct action_kw *action_http_res_custom(const char *kw) return action_lookup(&http_res_keywords.list, kw); } + +/* "show errors" handler for the CLI. Returns 0 if wants to continue, 1 to stop + * now. + */ +static int cli_parse_show_errors(char **args, struct appctx *appctx, void *private) +{ + if (!cli_has_level(appctx, ACCESS_LVL_OPER)) + return 1; + + if (*args[2]) + appctx->ctx.errors.iid = atoi(args[2]); + else + appctx->ctx.errors.iid = -1; + appctx->ctx.errors.px = NULL; + appctx->st2 = STAT_ST_INIT; + return 0; +} + +/* This function dumps all captured errors onto the stream interface's + * read buffer. It returns 0 if the output buffer is full and it needs + * to be called again, otherwise non-zero. + */ +static int cli_io_handler_show_errors(struct appctx *appctx) +{ + struct stream_interface *si = appctx->owner; + extern const char *monthname[12]; + + if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW))) + return 1; + + chunk_reset(&trash); + + if (!appctx->ctx.errors.px) { + /* the function had not been called yet, let's prepare the + * buffer for a response. + */ + struct tm tm; + + get_localtime(date.tv_sec, &tm); + chunk_appendf(&trash, "Total events captured on [%02d/%s/%04d:%02d:%02d:%02d.%03d] : %u\n", + tm.tm_mday, monthname[tm.tm_mon], tm.tm_year+1900, + tm.tm_hour, tm.tm_min, tm.tm_sec, (int)(date.tv_usec/1000), + error_snapshot_id); + + if (bi_putchk(si_ic(si), &trash) == -1) { + /* Socket buffer full. Let's try again later from the same point */ + si_applet_cant_put(si); + return 0; + } + + appctx->ctx.errors.px = proxy; + appctx->ctx.errors.buf = 0; + appctx->ctx.errors.bol = 0; + appctx->ctx.errors.ptr = -1; + } + + /* we have two inner loops here, one for the proxy, the other one for + * the buffer. + */ + while (appctx->ctx.errors.px) { + struct error_snapshot *es; + + if (appctx->ctx.errors.buf == 0) + es = &appctx->ctx.errors.px->invalid_req; + else + es = &appctx->ctx.errors.px->invalid_rep; + + if (!es->when.tv_sec) + goto next; + + if (appctx->ctx.errors.iid >= 0 && + appctx->ctx.errors.px->uuid != appctx->ctx.errors.iid && + es->oe->uuid != appctx->ctx.errors.iid) + goto next; + + if (appctx->ctx.errors.ptr < 0) { + /* just print headers now */ + + char pn[INET6_ADDRSTRLEN]; + struct tm tm; + int port; + + get_localtime(es->when.tv_sec, &tm); + chunk_appendf(&trash, " \n[%02d/%s/%04d:%02d:%02d:%02d.%03d]", + tm.tm_mday, monthname[tm.tm_mon], tm.tm_year+1900, + tm.tm_hour, tm.tm_min, tm.tm_sec, (int)(es->when.tv_usec/1000)); + + switch (addr_to_str(&es->src, pn, sizeof(pn))) { + case AF_INET: + case AF_INET6: + port = get_host_port(&es->src); + break; + default: + port = 0; + } + + switch (appctx->ctx.errors.buf) { + case 0: + chunk_appendf(&trash, + " frontend %s (#%d): invalid request\n" + " backend %s (#%d)", + appctx->ctx.errors.px->id, appctx->ctx.errors.px->uuid, + (es->oe->cap & PR_CAP_BE) ? es->oe->id : "", + (es->oe->cap & PR_CAP_BE) ? es->oe->uuid : -1); + break; + case 1: + chunk_appendf(&trash, + " backend %s (#%d): invalid response\n" + " frontend %s (#%d)", + appctx->ctx.errors.px->id, appctx->ctx.errors.px->uuid, + es->oe->id, es->oe->uuid); + break; + } + + chunk_appendf(&trash, + ", server %s (#%d), event #%u\n" + " src %s:%d, session #%d, session flags 0x%08x\n" + " HTTP msg state %d, msg flags 0x%08x, tx flags 0x%08x\n" + " HTTP chunk len %lld bytes, HTTP body len %lld bytes\n" + " buffer flags 0x%08x, out %d bytes, total %lld bytes\n" + " pending %d bytes, wrapping at %d, error at position %d:\n \n", + es->srv ? es->srv->id : "", es->srv ? es->srv->puid : -1, + es->ev_id, + pn, port, es->sid, es->s_flags, + es->state, es->m_flags, es->t_flags, + es->m_clen, es->m_blen, + es->b_flags, es->b_out, es->b_tot, + es->len, es->b_wrap, es->pos); + + if (bi_putchk(si_ic(si), &trash) == -1) { + /* Socket buffer full. Let's try again later from the same point */ + si_applet_cant_put(si); + return 0; + } + appctx->ctx.errors.ptr = 0; + appctx->ctx.errors.sid = es->sid; + } + + if (appctx->ctx.errors.sid != es->sid) { + /* the snapshot changed while we were dumping it */ + chunk_appendf(&trash, + " WARNING! update detected on this snapshot, dump interrupted. Please re-check!\n"); + if (bi_putchk(si_ic(si), &trash) == -1) { + si_applet_cant_put(si); + return 0; + } + goto next; + } + + /* OK, ptr >= 0, so we have to dump the current line */ + while (es->buf && appctx->ctx.errors.ptr < es->len && appctx->ctx.errors.ptr < global.tune.bufsize) { + int newptr; + int newline; + + newline = appctx->ctx.errors.bol; + newptr = dump_text_line(&trash, es->buf, global.tune.bufsize, es->len, &newline, appctx->ctx.errors.ptr); + if (newptr == appctx->ctx.errors.ptr) + return 0; + + if (bi_putchk(si_ic(si), &trash) == -1) { + /* Socket buffer full. Let's try again later from the same point */ + si_applet_cant_put(si); + return 0; + } + appctx->ctx.errors.ptr = newptr; + appctx->ctx.errors.bol = newline; + }; + next: + appctx->ctx.errors.bol = 0; + appctx->ctx.errors.ptr = -1; + appctx->ctx.errors.buf++; + if (appctx->ctx.errors.buf > 1) { + appctx->ctx.errors.buf = 0; + appctx->ctx.errors.px = appctx->ctx.errors.px->next; + } + } + + /* dump complete */ + return 1; +} + +/* register cli keywords */ +static struct cli_kw_list cli_kws = {{ },{ + { { "show", "errors", NULL }, + "show errors : report last request and response errors for each proxy", + cli_parse_show_errors, cli_io_handler_show_errors, NULL, + }, + {{},} +}}; + /************************************************************************/ /* All supported ACL keywords must be declared here. */ /************************************************************************/ @@ -13076,6 +13268,7 @@ static void __http_protocol_init(void) sample_register_convs(&sample_conv_kws); http_req_keywords_register(&http_req_actions); http_res_keywords_register(&http_res_actions); + cli_register_kw(&cli_kws); }