diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h index cf9ae29b8..844879ab2 100644 --- a/include/proto/dumpstats.h +++ b/include/proto/dumpstats.h @@ -49,6 +49,7 @@ void stats_dump_raw_to_buffer(struct session *s, struct buffer *req); int stats_dump_http(struct session *s, struct buffer *rep, struct uri_auth *uri); int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri); void stats_dump_sess_to_buffer(struct session *s, struct buffer *rep); +void stats_dump_errors_to_buffer(struct session *s, struct buffer *rep); #endif /* _PROTO_DUMPSTATS_H */ diff --git a/include/types/session.h b/include/types/session.h index fb6334771..356bdee61 100644 --- a/include/types/session.h +++ b/include/types/session.h @@ -204,6 +204,14 @@ struct session { struct { struct bref bref; } sess; + struct { + int iid; /* if >= 0, ID of the proxy to filter on */ + struct proxy *px; /* current proxy being dumped, NULL = not started yet. */ + unsigned int buf; /* buffer being dumped, 0 = req, 1 = rep */ + unsigned int sid; /* session ID of error being dumped */ + int ptr; /* <0: headers, >=0 : text pointer to restart from */ + int bol; /* pointer to beginning of current line */ + } errors; } data_ctx; /* used by produce_content to dump the stats right now */ unsigned int uniq_id; /* unique ID used for the traces */ }; diff --git a/src/dumpstats.c b/src/dumpstats.c index b12fbd0b1..dd047b76c 100644 --- a/src/dumpstats.c +++ b/src/dumpstats.c @@ -1222,6 +1222,212 @@ void stats_dump_sess_to_buffer(struct session *s, struct buffer *rep) } } +/* print a line of error buffer (limited to 70 bytes) to . The format is : + * <2 spaces> <70 chars max> <\n> + * which is 60 chars per line. Non-printable chars \t, \n, \r and \e are + * encoded in C format. Other non-printable chars are encoded "\xHH". Original + * lines are respected within the limit of 70 output chars. Lines that are + * continuation of a previous truncated line begin with "+" instead of " " + * after the offset. The new pointer is returned. + */ +static int dump_error_line(struct chunk *out, int size, + struct error_snapshot *err, int *line, int ptr) +{ + int end; + unsigned char c; + + end = out->len + 80; + if (end > size) + return ptr; + + chunk_printf(out, size, " %05d%c ", ptr, (ptr == *line) ? ' ' : '+'); + + while (ptr < err->len) { + c = err->buf[ptr]; + if (isprint(c)) { + if (out->len > end - 2) + break; + out->str[out->len++] = c; + } else if (c == '\t' || c == '\n' || c == '\r' || c == '\e') { + if (out->len > end - 3) + break; + out->str[out->len++] = '\\'; + switch (c) { + case '\t': c = 't'; break; + case '\n': c = 'n'; break; + case '\r': c = 'r'; break; + case '\e': c = 'e'; break; + } + out->str[out->len++] = c; + } else { + if (out->len > end - 5) + break; + out->str[out->len++] = '\\'; + out->str[out->len++] = 'x'; + out->str[out->len++] = hextab[(c >> 4) & 0xF]; + out->str[out->len++] = hextab[c & 0xF]; + } + if (err->buf[ptr++] == '\n') { + /* we had a line break, let's return now */ + out->str[out->len++] = '\n'; + *line = ptr; + return ptr; + } + } + /* we have an incomplete line, we return it as-is */ + out->str[out->len++] = '\n'; + return ptr; +} + +/* This function is called to send output to the response buffer. + * It dumps the errors logged in proxies onto the output buffer . + * Expects to be called with client socket shut down on input. + * s->data_ctx must have been zeroed first, and the flags properly set. + * It automatically clears the HIJACK bit from the response buffer. + */ +void stats_dump_errors_to_buffer(struct session *s, struct buffer *rep) +{ + extern const char *monthname[12]; + struct chunk msg; + + if (unlikely(rep->flags & (BF_WRITE_ERROR|BF_SHUTW))) { + s->data_state = DATA_ST_FIN; + buffer_stop_hijack(rep); + s->ana_state = STATS_ST_CLOSE; + return; + } + + if (s->ana_state != STATS_ST_REP) + return; + + msg.len = 0; + msg.str = trash; + + if (!s->data_ctx.errors.px) { + /* the function had not been called yet, let's prepare the + * buffer for a response. + */ + stream_int_retnclose(rep->cons, &msg); + s->data_ctx.errors.px = proxy; + s->data_ctx.errors.buf = 0; + s->data_ctx.errors.bol = 0; + s->data_ctx.errors.ptr = -1; + } + + /* we have two inner loops here, one for the proxy, the other one for + * the buffer. + */ + while (s->data_ctx.errors.px) { + struct error_snapshot *es; + + if (s->data_ctx.errors.buf == 0) + es = &s->data_ctx.errors.px->invalid_req; + else + es = &s->data_ctx.errors.px->invalid_rep; + + if (!es->when.tv_sec) + goto next; + + if (s->data_ctx.errors.iid >= 0 && + s->data_ctx.errors.px->uuid != s->data_ctx.errors.iid && + es->oe->uuid != s->data_ctx.errors.iid) + goto next; + + if (s->data_ctx.errors.ptr < 0) { + /* just print headers now */ + + char pn[INET6_ADDRSTRLEN]; + struct tm tm; + + get_localtime(es->when.tv_sec, &tm); + chunk_printf(&msg, sizeof(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, es->when.tv_usec/1000); + + + if (es->src.ss_family == AF_INET) + inet_ntop(AF_INET, + (const void *)&((struct sockaddr_in *)&es->src)->sin_addr, + pn, sizeof(pn)); + else + inet_ntop(AF_INET6, + (const void *)&((struct sockaddr_in6 *)(&es->src))->sin6_addr, + pn, sizeof(pn)); + + switch (s->data_ctx.errors.buf) { + case 0: + chunk_printf(&msg, sizeof(trash), + " frontend %s (#%d): invalid request\n" + " src %s, session #%d, backend %s (#%d), server %s (#%d)\n" + " request length %d bytes, error at position %d:\n\n", + s->data_ctx.errors.px->id, s->data_ctx.errors.px->uuid, + pn, es->sid, es->oe->id, es->oe->uuid, + es->srv ? es->srv->id : "", + es->srv ? es->srv->puid : -1, + es->len, es->pos); + break; + case 1: + chunk_printf(&msg, sizeof(trash), + " backend %s (#%d) : invalid response\n" + " src %s, session #%d, frontend %s (#%d), server %s (#%d)\n" + " response length %d bytes, error at position %d:\n\n", + s->data_ctx.errors.px->id, s->data_ctx.errors.px->uuid, + pn, es->sid, es->oe->id, es->oe->uuid, + es->srv ? es->srv->id : "", + es->srv ? es->srv->puid : -1, + es->len, es->pos); + break; + } + + if (buffer_write_chunk(rep, &msg) >= 0) { + /* Socket buffer full. Let's try again later from the same point */ + return; + } + s->data_ctx.errors.ptr = 0; + s->data_ctx.errors.sid = es->sid; + } + + if (s->data_ctx.errors.sid != es->sid) { + /* the snapshot changed while we were dumping it */ + chunk_printf(&msg, sizeof(trash), + " WARNING! update detected on this snapshot, dump interrupted. Please re-check!\n"); + if (buffer_write_chunk(rep, &msg) >= 0) + return; + goto next; + } + + /* OK, ptr >= 0, so we have to dump the current line */ + while (s->data_ctx.errors.ptr < es->len) { + int newptr; + int newline; + + newline = s->data_ctx.errors.bol; + newptr = dump_error_line(&msg, sizeof(trash), es, &newline, s->data_ctx.errors.ptr); + if (newptr == s->data_ctx.errors.ptr) + return; + + if (buffer_write_chunk(rep, &msg) >= 0) { + /* Socket buffer full. Let's try again later from the same point */ + return; + } + s->data_ctx.errors.ptr = newptr; + s->data_ctx.errors.bol = newline; + }; + next: + s->data_ctx.errors.bol = 0; + s->data_ctx.errors.ptr = -1; + s->data_ctx.errors.buf++; + if (s->data_ctx.errors.buf > 1) { + s->data_ctx.errors.buf = 0; + s->data_ctx.errors.px = s->data_ctx.errors.px->next; + } + } + + /* dump complete */ + buffer_stop_hijack(rep); + s->ana_state = STATS_ST_CLOSE; +} + static struct cfg_kw_list cfg_kws = {{ },{ { CFG_GLOBAL, "stats", stats_parse_global }, diff --git a/src/proto_uxst.c b/src/proto_uxst.c index a3bc98dc3..4b24209a6 100644 --- a/src/proto_uxst.c +++ b/src/proto_uxst.c @@ -615,6 +615,15 @@ int unix_sock_parse_request(struct session *s, char *line) s->ana_state = STATS_ST_REP; buffer_install_hijacker(s, s->rep, stats_dump_sess_to_buffer); } + else if (strcmp(args[1], "errors") == 0) { + if (*args[2]) + s->data_ctx.errors.iid = atoi(args[2]); + else + s->data_ctx.errors.iid = -1; + s->data_ctx.errors.px = NULL; + s->ana_state = STATS_ST_REP; + buffer_install_hijacker(s, s->rep, stats_dump_errors_to_buffer); + } else { /* neither "stat" nor "info" nor "sess" */ return 0; }