]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] implement error dump on unix socket with "show errors"
authorWilly Tarreau <w@1wt.eu>
Wed, 4 Mar 2009 14:53:18 +0000 (15:53 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 4 Mar 2009 14:53:18 +0000 (15:53 +0100)
The new "show errors" command sent on a unix socket will dump
all captured request and response errors for all proxies. It is
also possible to bound the log to frontends and backends whose
ID is passed as an optional parameter.

The output provides information about frontend, backend, server,
session ID, source address, error type, and error position along
with a complete dump of the request or response which has caused
the error.

If a new error scratches the one currently being reported, then
the dump is aborted with a warning message, and processing goes
on to next error.

include/proto/dumpstats.h
include/types/session.h
src/dumpstats.c
src/proto_uxst.c

index cf9ae29b8722dd430a584e1627fe78744ce0e980..844879ab2487f45381df3d30509a3d5557dbf43e 100644 (file)
@@ -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 */
index fb63347712ec5a7d84da04ab2ab24e0a1905911c..356bdee61b622795fcc2160b0b056b5a5ac8ec75 100644 (file)
@@ -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 */
 };
index b12fbd0b1ef226afe26c0dbdd3b2facde2d68165..dd047b76c249b71f710cd0afd84f273fc6b41431 100644 (file)
@@ -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 <out>. The format is :
+ * <2 spaces> <offset=5 digits> <space or plus> <space> <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 <rep>.
+ * 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 : "<NONE>",
+                                            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 : "<NONE>",
+                                            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 },
index a3bc98dc3683ff2aa74c827f4465a0b5c86bbb40..4b24209a64f3d90f2af0e6be3eb027ac0eefd207 100644 (file)
@@ -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;
                }