diff --git a/doc/configuration.txt b/doc/configuration.txt index b1c20e3cb..f1a388e5f 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -10235,6 +10235,11 @@ Special variable "%o" may be used to propagate its flags to all other variables on the same format string. This is particularly handy with quoted string formats ("Q"). +If a variable is named between square brackets ('[' .. ']') then it is used +as a pattern extraction rule (see section 7.8). This it useful to add some +less common information such as the client's SSL certificate's DN, or to log +the key that would be used to store an entry into a stick table. + Note: spaces must be escaped. A space character is considered as a separator. HAproxy will automatically merge consecutive separators. diff --git a/include/types/log.h b/include/types/log.h index 0ed79d414..2a3a14791 100644 --- a/include/types/log.h +++ b/include/types/log.h @@ -39,7 +39,7 @@ enum { LOG_FMT_TEXT = 0, /* raw text */ - + LOG_FMT_EXPR, /* sample expression */ LOG_FMT_SEPARATOR, /* separator replaced by one space */ LOG_FMT_VARIABLE, @@ -106,21 +106,25 @@ enum { LF_STARTVAR, // % in text LF_STARG, // after '%{' and berore '}' LF_EDARG, // '}' after '%{' + LF_STEXPR, // after '%[' or '%{..}[' and berore ']' + LF_EDEXPR, // ']' after '%[' LF_END, // \0 found }; struct logformat_node { struct list list; - int type; - int options; - char *arg; + int type; // LOG_FMT_* + int options; // LOG_OPT_* + char *arg; // text for LOG_FMT_TEXT, arg for others + void *expr; // for use with LOG_FMT_EXPR }; #define LOG_OPT_HEXA 0x00000001 #define LOG_OPT_MANDATORY 0x00000002 #define LOG_OPT_QUOTE 0x00000004 - +#define LOG_OPT_REQ_CAP 0x00000008 +#define LOG_OPT_RES_CAP 0x00000010 /* fields that need to be logged. They appear as flags in session->logs.logwait */ diff --git a/src/log.c b/src/log.c index a1bc78fdc..ae7d12217 100644 --- a/src/log.c +++ b/src/log.c @@ -33,6 +33,7 @@ #include #include +#include #include #ifdef USE_OPENSSL #include @@ -303,6 +304,51 @@ void add_to_logformat_list(char *start, char *end, int type, struct list *list_f } } +/* + * Parse the sample fetch expression and add a node to upon + * success. At the moment, sample converters are not yet supported but fetch arguments + * should work. + */ +void add_sample_to_logformat_list(char *text, char *arg, int arg_len, struct proxy *curpx, struct list *list_format, int options) +{ + char *cmd[2]; + struct sample_expr *expr; + struct logformat_node *node; + int cmd_arg; + + cmd[0] = text; + cmd[1] = ""; + cmd_arg = 0; + + expr = sample_parse_expr(cmd, &cmd_arg, trash.str, trash.size); + if (!expr) { + Warning("log-format: sample fetch <%s> failed with : %s\n", text, trash.str); + return; + } + + node = calloc(1, sizeof(struct logformat_node)); + node->type = LOG_FMT_EXPR; + node->expr = expr; + node->options = options; + + if (arg_len) { + node->arg = my_strndup(arg, arg_len); + parse_logformat_var_args(node->arg, node); + } + if (expr->fetch->cap & SMP_CAP_REQ) + node->options |= LOG_OPT_REQ_CAP; /* fetch method is request-compatible */ + + if (expr->fetch->cap & SMP_CAP_RES) + node->options |= LOG_OPT_RES_CAP; /* fetch method is response-compatible */ + + /* check if we need to allocate an hdr_idx struct for HTTP parsing */ + /* Note, we may also need to set curpx->to_log with certain fetches */ + if (expr->fetch->cap & SMP_CAP_L7) + curpx->acl_requires |= ACL_USE_L7_ANY; + + LIST_ADDQ(list_format, &node->list); +} + /* * Parse the log_format string and fill a linked list. * Variable name are preceded by % and composed by characters [a-zA-Z0-9]* : %varname @@ -346,12 +392,16 @@ void parse_logformat_string(char *str, struct proxy *curproxy, struct list *list */ switch (pformat) { case LF_STARTVAR: // text immediately following a '%' - arg = NULL; + arg = NULL; var = NULL; arg_len = var_len = 0; if (*str == '{') { // optional argument cformat = LF_STARG; arg = str + 1; } + else if (*str == '[') { + cformat = LF_STEXPR; + var = str + 1; // store expr in variable name + } else if (isalnum((int)*str)) { // variable name cformat = LF_VAR; var = str; @@ -371,23 +421,35 @@ void parse_logformat_string(char *str, struct proxy *curproxy, struct list *list break; case LF_EDARG: // text immediately following '%{arg}' - if (isalnum((int)*str)) { // variable name + if (*str == '[') { + cformat = LF_STEXPR; + var = str + 1; // store expr in variable name + break; + } + else if (isalnum((int)*str)) { // variable name cformat = LF_VAR; var = str; break; } - Warning("Skipping isolated argument in log-format line : '%%{%s}'\n", arg); cformat = LF_INIT; break; + case LF_STEXPR: // text immediately following '%[' + if (*str == ']') { // end of arg + cformat = LF_EDEXPR; + var_len = str - var; + *str = 0; // needed for parsing the expression + } + break; + case LF_VAR: // text part of a variable name var_len = str - var; if (!isalnum((int)*str)) cformat = LF_INIT; // not variable name anymore break; - default: // LF_INIT, LF_TEXT, LF_SEPARATOR, LF_END + default: // LF_INIT, LF_TEXT, LF_SEPARATOR, LF_END, LF_EDEXPR cformat = LF_INIT; } @@ -405,6 +467,9 @@ void parse_logformat_string(char *str, struct proxy *curproxy, struct list *list case LF_VAR: parse_logformat_var(arg, arg_len, var, var_len, curproxy, list_format, &options); break; + case LF_STEXPR: + add_sample_to_logformat_list(var, arg, arg_len, curproxy, list_format, options); + break; case LF_TEXT: case LF_SEPARATOR: add_to_logformat_list(sp, str, pformat, list_format); @@ -414,8 +479,8 @@ void parse_logformat_string(char *str, struct proxy *curproxy, struct list *list } } - if (pformat == LF_STARTVAR || pformat == LF_STARG) - Warning("Ignoring end of truncated log-format line after '%s'\n", arg ? arg : "%"); + if (pformat == LF_STARTVAR || pformat == LF_STARG || pformat == LF_STEXPR) + Warning("Ignoring end of truncated log-format line after '%s'\n", var ? var : arg ? arg : "%"); } /* @@ -823,8 +888,9 @@ int build_logline(struct session *s, char *dst, size_t maxsize, struct list *lis list_for_each_entry(tmp, list_format, list) { const char *src = NULL; - switch (tmp->type) { + struct sample *key; + switch (tmp->type) { case LOG_FMT_SEPARATOR: if (!last_isspace) { LOGCHAR(' '); @@ -841,6 +907,21 @@ int build_logline(struct session *s, char *dst, size_t maxsize, struct list *lis last_isspace = 0; break; + case LOG_FMT_EXPR: // sample expression, may be request or response + key = NULL; + if (tmp->options & LOG_OPT_REQ_CAP) + key = sample_fetch_string(be, s, txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, tmp->expr); + if (!key && (tmp->options & LOG_OPT_RES_CAP)) + key = sample_fetch_string(be, s, txn, SMP_OPT_DIR_RES|SMP_OPT_FINAL, tmp->expr); + if (!key) + break; + ret = lf_text_len(tmplog, key->data.str.str, key->data.str.len, dst + maxsize - tmplog, tmp); + if (ret == 0) + goto out; + tmplog = ret; + last_isspace = 0; + break; + case LOG_FMT_CLIENTIP: // %ci ret = lf_ip(tmplog, (struct sockaddr *)&s->req->prod->conn->addr.from, dst + maxsize - tmplog, tmp);