]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-master: Add improved log filter parsing
authorTimo Sirainen <timo.sirainen@dovecot.fi>
Mon, 16 Apr 2018 12:00:27 +0000 (15:00 +0300)
committerTimo Sirainen <timo.sirainen@dovecot.fi>
Mon, 21 May 2018 09:03:11 +0000 (09:03 +0000)
It now supports parenthesis to perform ANDs within a query. For example:
"(ruleA1 ruleA2) ruleB (ruleC1 ruleC2 ruleC3)" has 3 ORed rules.

Rules can contain:

 - event:<name>
 - source:<filename>[:<line number>]
 - field:<key>=<value> can be used multiple times
 - cat[egory]:<value> can be used multiple times

For example:

event:http_request_finished (cat:error cat:storage)

This matches either the "http_request_finished" named event, or
alternatively any error event that belongs to "storage" category.

src/lib-master/master-service-settings.c

index 14ee16197d26a18047a2183705f4522ce70c902a..5fee08a09693a87868c0594d34ea103d058b15f9 100644 (file)
@@ -106,20 +106,111 @@ const struct setting_parser_info master_service_setting_parser_info = {
 };
 
 /* <settings checks> */
+static int parse_query(const char *str, struct event_filter_query *query_r,
+                      const char **error_r)
+{
+       ARRAY_TYPE(const_string) categories = ARRAY_INIT;
+       ARRAY(struct event_filter_field) fields = ARRAY_INIT;
+
+       i_zero(query_r);
+       do {
+               while (*str == ' ')
+                       str++;
+               const char *p = strchr(str, ' ');
+               if (p != NULL)
+                       str = t_strdup_until(str, p++);
+
+               if (strncmp(str, "event:", 6) == 0) {
+                       query_r->name = str+6;
+               } else if (strncmp(str, "source:", 7) == 0) {
+                       const char *linep = strchr(str+7, ':');
+                       if (linep == NULL) {
+                               /* filename only - match to all line numbers */
+                               query_r->source_filename = str+7;
+                       } else {
+                               query_r->source_filename = t_strdup_until(str+7, linep);
+                               if (str_to_uint(linep+1, &query_r->source_linenum) < 0) {
+                                       *error_r = t_strdup_printf(
+                                               "Invalid line number in '%s'", str);
+                                       return -1;
+                               }
+                       }
+               } else if (strncmp(str, "field:", 6) == 0) {
+                       const char *value = strchr(str+6, '=');
+                       if (value == NULL) {
+                               *error_r = t_strdup_printf(
+                                       "Missing '=' in '%s'", str);
+                               return -1;
+                       }
+                       if (!array_is_created(&fields))
+                               t_array_init(&fields, 4);
+                       struct event_filter_field *field =
+                               array_append_space(&fields);
+                       field->key = t_strdup_until(str+6, value);
+                       field->value = value+1;
+               } else if (strncmp(str, "cat:", 4) == 0 ||
+                          strncmp(str, "category:", 9) == 0) {
+                       if (!array_is_created(&categories))
+                               t_array_init(&categories, 4);
+                       str = strchr(str, ':');
+                       i_assert(str != NULL);
+                       str++;
+                       array_append(&categories, &str, 1);
+               } else {
+                       *error_r = t_strdup_printf("Unknown event '%s'", str);
+                       return -1;
+               }
+               str = p;
+       } while (str != NULL);
+
+       if (array_is_created(&categories)) {
+               array_append_zero(&categories);
+               query_r->categories = array_idx(&categories, 0);
+       }
+       if (array_is_created(&fields)) {
+               array_append_zero(&fields);
+               query_r->fields = array_idx(&fields, 0);
+       }
+       return 0;
+}
+
 int master_service_log_filter_parse(struct event_filter *filter, const char *str,
                                    const char **error_r)
 {
-       const char *categories[2] = { NULL, NULL };
-       struct event_filter_query query = {
-               .categories = categories
-       };
-
-       /* FIXME: we should support more complicated filters */
-       const char *const *args = t_strsplit_spaces(str, " ");
-       for (unsigned int i = 0; args[i] != NULL; i++) {
-               categories[0] = args[i];
+       struct event_filter_query query;
+       const char *p;
+
+       while (*str != '\0') {
+               if (*str == ' ') {
+                       str++;
+                       continue;
+               }
+
+               if (*str == '(') {
+                       /* everything inside (...) is a single query */
+                       str++;
+                       p = strchr(str, ')');
+                       if (p == NULL) {
+                               *error_r = "Missing ')'";
+                               return -1;
+                       }
+                       if (parse_query(t_strdup_until(str, p), &query, error_r) < 0)
+                               return -1;
+                       str = p+1;
+               } else if ((p = strchr(str, ' ')) != NULL) {
+                       /* parse a single-word query in the middle */
+                       if (parse_query(t_strdup_until(str, p), &query, error_r) < 0)
+                               return -1;
+                       str = p+1;
+               } else {
+                       /* single-word last query */
+                       if (parse_query(str, &query, error_r) < 0)
+                               return -1;
+                       str = "";
+               }
                event_filter_add(filter, &query);
        }
+
        *error_r = NULL;
        return 0;
 }