MEDIUM: log: Unique ID

The Unique ID, is an ID generated with several informations. You can use
a log-format string to customize it, with the "unique-id-format" keyword,
and insert it in the request header, with the "unique-id-header" keyword.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 66b61ff..8f50213 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1117,6 +1117,8 @@
 timeout srvtimeout          (deprecated)  X          -         X         X
 timeout tarpit                            X          X         X         X
 transparent                 (deprecated)  X          -         X         X
+unique-id-format                          X          X         X         -
+unique-id-header                          X          X         X         -
 use_backend                               -          X         X         -
 use-server                                -          -         X         X
 ------------------------------------+----------+----------+---------+---------
@@ -6564,6 +6566,60 @@
 
   See also: "option transparent"
 
+unique-id-format <string>
+  Generate a unique ID for each request.
+  May be used in sections :   defaults | frontend | listen | backend
+                                  yes  |    yes   |   yes  |   no
+  Arguments :
+    <string>   is a log-format string.
+
+    This keyword creates a ID for each request using the custom log format. A
+    unique ID is useful to trace a request passing through many components of
+    a complex infrastructure. The newly created ID may also be logged using the
+    %ID tag the log-format string.
+
+    The format should be composed from elements that are guaranteed to be
+    unique when combined together. For instance, if multiple haproxy instances
+    are involved, it might be important to include the node name. It is often
+    needed to log the incoming connection's source and destination addresses
+    and ports. Note that since multiple requests may be performed over the same
+    connection, including a request counter may help differentiate them.
+    Similarly, a timestamp may protect against a rollover of the counter.
+    Logging the process ID will avoid collisions after a service restart.
+
+    It is recommended to use hexadecimal notation for many fields since it
+    makes them more compact and saves space in logs.
+
+    Example:
+
+        unique-id-format %{+X}o\ %Ci:%Cp_%Fi:%Fp_%Ts_%rt:%pid
+
+        will generate:
+
+               7F000001:8296_7F00001E:1F90_4F7B0A69_0003:790A
+
+  See also: "unique-id-header"
+
+unique-id-header <name>
+  Add a unique ID header in the HTTP request.
+  May be used in sections :   defaults | frontend | listen | backend
+                                  yes  |    yes   |   yes  |   no
+  Arguments :
+    <name>   is the name of the header.
+
+    Add a unique-id header in the HTTP request sent to the server, using the
+    unique-id-format. It can't work if the unique-id-format doesn't exist.
+
+    Example:
+
+        unique-id-format %{+X}o\ %Ci:%Cp_%Fi:%Fp_%Ts_%rt:%pid
+        unique-id-header X-Unique-ID
+
+        will generate:
+
+           X-Unique-ID: 7F000001:8296_7F00001E:1F90_4F7B0A69_0003:790A
+
+    See also: "unique-id-format"
 
 use_backend <backend> if <condition>
 use_backend <backend> unless <condition>
@@ -8919,6 +8975,7 @@
   |   | %Fi  | frontend_ip                                   | IP          |
   |   | %Fp  | frontend_port                                 | numeric     |
   |   | %H   | hostname                                      | string      |
+  |   | %ID  | unique-id                                     | string      |
   |   | %Si  | server_IP                                     | IP          |
   |   | %Sp  | server_port                                   | numeric     |
   |   | %T   | gmt_date_time                                 | date        |
diff --git a/include/proto/log.h b/include/proto/log.h
index 0e41eca..5adf674 100644
--- a/include/proto/log.h
+++ b/include/proto/log.h
@@ -33,6 +33,7 @@
 #include <types/session.h>
 
 extern struct pool_head *pool2_requri;
+extern struct pool_head *pool2_uniqueid;
 
 extern char *log_format;
 extern char default_tcp_log_format[];
diff --git a/include/types/log.h b/include/types/log.h
index ad96e9a..269fb46 100644
--- a/include/types/log.h
+++ b/include/types/log.h
@@ -31,6 +31,8 @@
 #define NB_LOG_FACILITIES       24
 #define NB_LOG_LEVELS           8
 #define SYSLOG_PORT             514
+#define UNIQUEID_LEN            128
+
 
 /* lists of fields that can be logged */
 enum {
@@ -86,6 +88,7 @@
 	LOG_FMT_HDRRESPONSLIST,
 	LOG_FMT_REQ,
 	LOG_FMT_HOSTNAME,
+	LOG_FMT_UNIQUEID,
 };
 
 /* enum for parse_logformat */
diff --git a/include/types/proxy.h b/include/types/proxy.h
index d69a914..aa6cec8 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -290,6 +290,8 @@
 	struct proxy *next;
 	struct list logsrvs;
 	struct list logformat; 			/* log_format linked list */
+	char *header_unique_id; 		/* unique-id header */
+	struct list format_unique_id;		/* unique-id format */
 	int to_log;				/* things to be logged (LW_*) */
 	int stop_time;                          /* date to stop listening, when stopping != 0 (int ticks) */
 	struct hdr_exp *req_exp;		/* regular expressions for request headers */
diff --git a/include/types/session.h b/include/types/session.h
index 5678e5c..7539c0c 100644
--- a/include/types/session.h
+++ b/include/types/session.h
@@ -207,6 +207,7 @@
 	void (*srv_error)(struct session *s,	/* the function to call upon unrecoverable server errors (or NULL) */
 			  struct stream_interface *si);
 	unsigned int uniq_id;			/* unique ID used for the traces */
+	char *unique_id;			/* custom unique ID */
 };
 
 /* parameters to configure tracked counters */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index aa57b9b..1267cd7 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -1550,6 +1550,18 @@
 			LIST_ADDQ(&curproxy->logformat, &node->list);
 		}
 
+		/* copy default unique_id to curproxy */
+		list_for_each_entry(tmplf, &defproxy.format_unique_id, list) {
+			struct logformat_node *node = malloc(sizeof(struct logformat_node));
+			memcpy(node, tmplf, sizeof(struct logformat_node));
+			LIST_INIT(&node->list);
+			LIST_ADDQ(&curproxy->format_unique_id, &node->list);
+		}
+
+		/* copy default header unique id */
+		if (defproxy.header_unique_id)
+			curproxy->header_unique_id = strdup(defproxy.header_unique_id);
+
 		curproxy->grace  = defproxy.grace;
 		curproxy->conf.used_listener_id = EB_ROOT;
 		curproxy->conf.used_server_id = EB_ROOT;
@@ -4591,7 +4603,27 @@
 
 			newsrv->prev_state = newsrv->state;
 		}
+	}
+
+	else if (strcmp(args[0], "unique-id-format") == 0) {
+		if (!*(args[1])) {
+			Alert("parsing [%s:%d] : %s expects an argument.\n", file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+		parse_logformat_string(args[1], curproxy, &curproxy->format_unique_id, PR_MODE_HTTP);
 	}
+
+	else if (strcmp(args[0], "unique-id-header") == 0) {
+		if (!*(args[1])) {
+			Alert("parsing [%s:%d] : %s expects an argument.\n", file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+		free(curproxy->header_unique_id);
+		curproxy->header_unique_id = strdup(args[1]);
+	}
+
 	else if (strcmp(args[0], "log-format") == 0) {
 		if (!*(args[1])) {
 			Alert("parsing [%s:%d] : %s expects an argument.\n", file, linenum, args[0]);
diff --git a/src/log.c b/src/log.c
index af2aee9..130d627 100644
--- a/src/log.c
+++ b/src/log.c
@@ -107,6 +107,7 @@
 	{ "pid", LOG_FMT_PID, PR_MODE_TCP, NULL }, /* log pid */
 	{ "rt", LOG_FMT_COUNTER, PR_MODE_TCP, NULL }, /* log counter */
 	{ "H", LOG_FMT_HOSTNAME, PR_MODE_TCP, NULL }, /* Hostname */
+	{ "ID", LOG_FMT_UNIQUEID, PR_MODE_HTTP, NULL }, /* Unique ID */
 	{ 0, 0, 0, NULL }
 };
 
@@ -1305,6 +1306,16 @@
 					last_isspace = 0;
 				}
 				break;
+
+			case LOG_FMT_UNIQUEID: // %ID
+				src = s->unique_id;
+				ret = lf_text(tmplog, src, maxsize - (tmplog - dst), tmp);
+				if (ret == NULL)
+					goto out;
+				tmplog = ret;
+				last_isspace = 0;
+				break;
+
 		}
 	}
 
diff --git a/src/proto_http.c b/src/proto_http.c
index 2f9d3e8..dd2f0d0 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -268,6 +268,7 @@
 	/* memory allocations */
 	pool2_requri = create_pool("requri", REQURI_LEN, MEM_F_SHARED);
 	pool2_capture = create_pool("capture", CAPTURE_LEN, MEM_F_SHARED);
+	pool2_uniqueid = create_pool("uniqueid", UNIQUEID_LEN, MEM_F_SHARED);
 }
 
 /*
@@ -858,6 +859,7 @@
 extern const char *monthname[12];
 struct pool_head *pool2_requri;
 struct pool_head *pool2_capture;
+struct pool_head *pool2_uniqueid;
 
 /*
  * Capture headers from message starting at <som> according to header list
@@ -2397,6 +2399,10 @@
 		}
 	}
 
+		if (!LIST_ISEMPTY(&s->fe->format_unique_id)) {
+			s->unique_id = pool_alloc2(pool2_uniqueid);
+		}
+
 	/* 4. We may have to convert HTTP/0.9 requests to HTTP/1.0 */
 	if (unlikely(msg->sl.rq.v_l == 0) && !http_upgrade_v09_to_v10(req, msg, txn))
 		goto return_bad_req;
@@ -3274,6 +3280,19 @@
 		get_srv_from_appsession(s, msg->sol + msg->sl.rq.u, msg->sl.rq.u_l);
 	}
 
+	/* add unique-id if "header-unique-id" is specified */
+
+	if (!LIST_ISEMPTY(&s->fe->format_unique_id))
+		build_logline(s, s->unique_id, UNIQUEID_LEN, &s->fe->format_unique_id);
+
+	if (s->fe->header_unique_id && s->unique_id) {
+		int ret = snprintf(trash, global.tune.bufsize, "%s: %s", s->fe->header_unique_id, s->unique_id);
+		if (ret < 0 || ret > global.tune.bufsize)
+			goto return_bad_req;
+		if(unlikely(http_header_add_tail(req, &txn->req, &txn->hdr_idx, trash) < 0))
+		   goto return_bad_req;
+	}
+
 	/*
 	 * 9: add X-Forwarded-For if either the frontend or the backend
 	 * asks for it.
@@ -7381,7 +7400,9 @@
 	pool_free2(pool2_capture, txn->cli_cookie);
 	pool_free2(pool2_capture, txn->srv_cookie);
 	pool_free2(apools.sessid, txn->sessid);
+	pool_free2(pool2_uniqueid, s->unique_id);
 
+	s->unique_id = NULL;
 	txn->sessid = NULL;
 	txn->uri = NULL;
 	txn->srv_cookie = NULL;
diff --git a/src/proxy.c b/src/proxy.c
index 7f15bb2..bb9fe57 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -439,6 +439,7 @@
 	LIST_INIT(&p->listener_queue);
 	LIST_INIT(&p->logsrvs);
 	LIST_INIT(&p->logformat);
+	LIST_INIT(&p->format_unique_id);
 
 	/* Timeouts are defined as -1 */
 	proxy_reset_timeouts(p);