From: Timo Sirainen Date: Tue, 9 Mar 2021 14:33:15 +0000 (+0200) Subject: lib: Fix global events to actually work X-Git-Tag: 2.3.18~367 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c8222386042a7d83fa10a489c58ee900772a5f5c;p=thirdparty%2Fdovecot%2Fcore.git lib: Fix global events to actually work Also add comments to clarify how exactly it works. --- diff --git a/src/lib/event-filter.c b/src/lib/event-filter.c index 5574f2bd2e..b4f8ce566e 100644 --- a/src/lib/event-filter.c +++ b/src/lib/event-filter.c @@ -469,6 +469,13 @@ event_has_category(struct event *event, struct event_filter_node *node, /* try also the parent events */ event = event_get_parent(event); } + /* check also the global event and its parents */ + event = event_get_global(); + while (event != NULL) { + if (event_has_category_nonrecursive(event, wanted_category)) + return TRUE; + event = event_get_parent(event); + } return FALSE; } diff --git a/src/lib/lib-event.c b/src/lib/lib-event.c index 9afa48968a..f1c45cbe5c 100644 --- a/src/lib/lib-event.c +++ b/src/lib/lib-event.c @@ -241,8 +241,9 @@ struct event *event_flatten(struct event *src) { struct event *dst; - /* If we don't have a parent, we have nothing to flatten. */ - if (src->parent == NULL) + /* If we don't have a parent or a global event, + we have nothing to flatten. */ + if (src->parent == NULL && current_global_event == NULL) return event_ref(src); /* We have to flatten the event. */ @@ -251,6 +252,8 @@ struct event *event_flatten(struct event *src) src->source_linenum); dst = event_set_name(dst, src->sending_name); + if (current_global_event != NULL) + event_flatten_recurse(dst, current_global_event, NULL); event_flatten_recurse(dst, src, NULL); dst->tv_created_ioloop = src->tv_created_ioloop; @@ -837,6 +840,14 @@ event_find_field_recursive(const struct event *event, const char *key) return field; event = event->parent; } while (event != NULL); + + /* check also the global event and its parents */ + event = event_get_global(); + while (event != NULL) { + if ((field = event_find_field_nonrecursive(event, key)) != NULL) + return field; + event = event->parent; + } return NULL; } @@ -889,8 +900,11 @@ event_find_field_recursive_str(const struct event *event, const char *key) ARRAY_TYPE(const_string) list; t_array_init(&list, 8); /* This is a bit different, because it needs to be merging - all of the parent events' lists together. */ + all of the parent events' and global events' lists + together. */ event_get_recursive_strlist(event, NULL, key, &list); + event_get_recursive_strlist(event_get_global(), NULL, + key, &list); return t_array_const_string_join(&list, ","); } } diff --git a/src/lib/lib-event.h b/src/lib/lib-event.h index dc27a79d8e..7d1b6da030 100644 --- a/src/lib/lib-event.h +++ b/src/lib/lib-event.h @@ -166,21 +166,32 @@ struct event *event_ref(struct event *event); freed. The current global event's refcount must not drop to 0. */ void event_unref(struct event **event); -/* Set the event to be the global default event used by i_error(), etc. - Returns the event parameter. The event must be explicitly popped before - it's freed. - - The global event stack is also an alternative nonpermanent hierarchy for - events. For example the global event can be "IMAP command SELECT", which - can be used for filtering events that happen while the SELECT command is - being executed. However, for the created struct mailbox the parent event - should be the mail_user, not the SELECT command. Otherwise everything else - that happens afterwards to the selected mailbox would also count towards - SELECT. This means that events shouldn't be using the current global event - as their parent event. */ +/* Set the event to be the global event and push it at the top of the global + event stack. Returns the event parameter. The event must be explicitly + popped before it's freed. + + The global event acts as the root event for all the events while they are + being emitted. The global events don't permanently affect the event + hierarchy. The global events are typically used to add extra fields to all + emitted events while some specific work is running. + + For example the global event can be "IMAP command SELECT", which can be used + for filtering events that happen while the SELECT command is being executed. + However, for the created struct mailbox the parent event should be the + mail_user, not the SELECT command. (If the mailbox used SELECT command as + the parent event, then any future event emitted via the mailbox event would + show SELECT command as the parent, even after SELECT had already finished.) + + The global event works the same as if all the events' roots were instead + pointing to the global event. Global events don't affect log prefixes. + + The created global events should use event_get_global() as their parent + event. Only the last pushed global event is used. */ struct event *event_push_global(struct event *event); -/* Pop the global event. Assert-crash if the current global event isn't the - given event parameter. Returns the new global event. */ +/* Pop the current global event and set the global event to the next one at + the top of the stack. Assert-crash if the current global event isn't the + given event parameter. Returns the next (now activated) global event in the + stack, or NULL if the stack is now empty. */ struct event *event_pop_global(struct event *event); /* Returns the current global event. */ struct event *event_get_global(void); @@ -332,10 +343,11 @@ void event_get_last_duration(const struct event *event, struct event_field * event_find_field_nonrecursive(const struct event *event, const char *key); /* Returns field for a given key, or NULL if it doesn't exist. If the key - isn't found from the event itself, find it from parent events. */ + isn't found from the event itself, find it from parent events, including + from the global event. */ const struct event_field * event_find_field_recursive(const struct event *event, const char *key); -/* Returns the given key's value as string, or NULL if it doesn't exist. +/* Same as event_find_field(), but return the value converted to a string. If the field isn't stored as a string, the result is allocated from data stack. */ const char * diff --git a/src/lib/test-event-filter.c b/src/lib/test-event-filter.c index ddb284f87a..d2cf9e0c89 100644 --- a/src/lib/test-event-filter.c +++ b/src/lib/test-event-filter.c @@ -61,6 +61,65 @@ static void test_event_filter_override_parent_fields(void) test_end(); } +static void test_event_filter_override_global_fields(void) +{ + struct event_filter *filter; + const char *error; + const struct failure_context failure_ctx = { + .type = LOG_TYPE_DEBUG + }; + + test_begin("event filter: override global fields"); + + struct event *global = event_create(NULL); + event_add_str(global, "str", "global_str"); + event_add_str(global, "global_str", "global_str"); + event_add_int(global, "int1", 0); + event_add_int(global, "int2", 5); + event_add_int(global, "global_int", 6); + event_push_global(global); + + struct event *local = event_create(NULL); + event_add_str(local, "str", "local_str"); + event_add_str(local, "local_str", "local_str"); + event_add_int(local, "int1", 6); + event_add_int(local, "int2", 0); + event_add_int(local, "local_int", 8); + + /* global matches: test a mix of global/local fields */ + filter = event_filter_create(); + test_assert(event_filter_parse("str=global_str AND int1=0 AND int2=5", filter, &error) == 0); + test_assert(event_filter_match(filter, global, &failure_ctx)); + test_assert(!event_filter_match(filter, local, &failure_ctx)); + event_filter_unref(&filter); + + /* global matches: test fields that exist only in global */ + filter = event_filter_create(); + test_assert(event_filter_parse("global_str=global_str AND global_int=6", filter, &error) == 0); + test_assert(event_filter_match(filter, global, &failure_ctx)); + test_assert(event_filter_match(filter, local, &failure_ctx)); + event_filter_unref(&filter); + + /* local matches: test a mix of global/local fields */ + filter = event_filter_create(); + test_assert(event_filter_parse("str=local_str AND int1=6 AND int2=0", filter, &error) == 0); + test_assert(event_filter_match(filter, local, &failure_ctx)); + test_assert(!event_filter_match(filter, global, &failure_ctx)); + event_filter_unref(&filter); + + /* local matches: test fields that exist only in local */ + filter = event_filter_create(); + test_assert(event_filter_parse("local_str=local_str AND local_int=8", filter, &error) == 0); + test_assert(event_filter_match(filter, local, &failure_ctx)); + test_assert(!event_filter_match(filter, global, &failure_ctx)); + event_filter_unref(&filter); + + event_pop_global(global); + event_unref(&global); + event_unref(&local); + test_end(); +} + static void test_event_filter_clear_parent_fields(void) { struct event_filter *filter; @@ -110,6 +169,57 @@ static void test_event_filter_clear_parent_fields(void) test_end(); } +static void test_event_filter_clear_global_fields(void) +{ + struct event_filter *filter; + const char *error; + const struct failure_context failure_ctx = { + .type = LOG_TYPE_DEBUG + }; + const char *keys[] = { "str", "int" }; + + test_begin("event filter: clear global fields"); + + struct event *global = event_create(NULL); + event_add_str(global, "str", "global_str"); + event_add_int(global, "int", 0); + event_push_global(global); + + struct event *local = event_create(NULL); + event_field_clear(local, "str"); + event_field_clear(local, "int"); + + for (unsigned int i = 0; i < N_ELEMENTS(keys); i++) { + /* match any value */ + const char *query = t_strdup_printf("%s=*", keys[i]); + filter = event_filter_create(); + test_assert(event_filter_parse(query, filter, &error) == 0); + + test_assert_idx(event_filter_match(filter, global, &failure_ctx), i); + test_assert_idx(!event_filter_match(filter, local, &failure_ctx), i); + event_filter_unref(&filter); + } + + /* match empty field */ + filter = event_filter_create(); + test_assert(event_filter_parse("str=\"\"", filter, &error) == 0); + test_assert(!event_filter_match(filter, global, &failure_ctx)); + test_assert(event_filter_match(filter, local, &failure_ctx)); + event_filter_unref(&filter); + + /* match nonexistent field */ + filter = event_filter_create(); + test_assert(event_filter_parse("nonexistent=\"\"", filter, &error) == 0); + test_assert(event_filter_match(filter, global, &failure_ctx)); + test_assert(event_filter_match(filter, local, &failure_ctx)); + event_filter_unref(&filter); + + event_pop_global(global); + event_unref(&global); + event_unref(&local); + test_end(); +} + static void test_event_filter_inc_int(void) { struct event_filter *filter; @@ -272,12 +382,69 @@ static void test_event_filter_strlist_recursive(void) test_end(); } +static void test_event_filter_strlist_global_events(void) +{ + struct event_filter *filter; + const struct failure_context failure_ctx = { + .type = LOG_TYPE_DEBUG + }; + + test_begin("event filter: match string list - global events"); + + struct event *global = event_create(NULL); + event_push_global(global); + + struct event *e = event_create(NULL); + + /* empty filter: global is non-empty */ + filter = event_filter_create(); + event_filter_parse("list1=\"\"", filter, NULL); + test_assert(event_filter_match(filter, e, &failure_ctx)); + event_strlist_append(global, "list1", "foo"); + test_assert(!event_filter_match(filter, e, &failure_ctx)); + event_filter_unref(&filter); + + /* matching filter: matches global */ + filter = event_filter_create(); + event_filter_parse("list2=global", filter, NULL); + /* empty: */ + test_assert(!event_filter_match(filter, e, &failure_ctx)); + /* set global but no local: */ + event_strlist_append(global, "list2", "global"); + test_assert(event_filter_match(filter, e, &failure_ctx)); + /* set local to non-matching: */ + event_strlist_append(e, "list2", "local"); + test_assert(event_filter_match(filter, e, &failure_ctx)); + event_filter_unref(&filter); + + /* matching filter: matches local */ + filter = event_filter_create(); + event_filter_parse("list3=local", filter, NULL); + /* empty: */ + test_assert(!event_filter_match(filter, e, &failure_ctx)); + /* set local but no global: */ + event_strlist_append(e, "list3", "local"); + test_assert(event_filter_match(filter, e, &failure_ctx)); + /* set global to non-matching: */ + event_strlist_append(e, "list3", "global"); + test_assert(event_filter_match(filter, e, &failure_ctx)); + event_filter_unref(&filter); + + event_unref(&e); + event_pop_global(global); + event_unref(&global); + test_end(); +} + void test_event_filter(void) { test_event_filter_override_parent_fields(); + test_event_filter_override_global_fields(); test_event_filter_clear_parent_fields(); + test_event_filter_clear_global_fields(); test_event_filter_inc_int(); test_event_filter_parent_category_match(); test_event_filter_strlist(); test_event_filter_strlist_recursive(); + test_event_filter_strlist_global_events(); }