diff --git a/docs/VictoriaLogs/LogsQL.md b/docs/VictoriaLogs/LogsQL.md index eefa211382..f1f4ecf705 100644 --- a/docs/VictoriaLogs/LogsQL.md +++ b/docs/VictoriaLogs/LogsQL.md @@ -589,11 +589,11 @@ See also: ### Exact prefix filter -Sometimes it is needed to find log messages starting with some prefix. This can be done with the `exact_prefix(...)` filter. +Sometimes it is needed to find log messages starting with some prefix. This can be done with the `exact("prefix"*)` filter. For example, the following query matches log messages, which start from `Processing request` prefix: ```logsql -exact_prefix("Processing request") +exact("Processing request"*) ``` This filter matches the following [log messages](https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#message-field): @@ -603,30 +603,30 @@ This filter matches the following [log messages](https://docs.victoriametrics.co It doesn't match the following log messages: -- `processing request foobar`, since the log message starts with lowercase `p`. Use `exact_prefix("processing request") OR exact_prefix("Processing request")` +- `processing request foobar`, since the log message starts with lowercase `p`. Use `exact("processing request"*) OR exact("Processing request"*)` query in this case. See [these docs](#logical-filter) for details. - `start: Processing request`, since the log message doesn't start with `Processing request`. Use `"Processing request"` query in this case. See [these docs](#phrase-filter) for details. -By default the `exact_prefix()` filter is applied to the [`_msg` field](https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#message-field). -Specify the [field name](https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#data-model) in front of the `exact_prefix()` filter and put a colon after it +By default the `exact()` filter is applied to the [`_msg` field](https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#message-field). +Specify the [field name](https://docs.victoriametrics.com/VictoriaLogs/keyConcepts.html#data-model) in front of the `exact()` filter and put a colon after it if it must be searched in the given field. For example, the following query returns log entries with `log.level` field, which starts with `err` prefix: ```logsql -log.level:exact_prefix("err") +log.level:exact("err"*) ``` Both the field name and the phrase can contain arbitrary [utf-8](https://en.wikipedia.org/wiki/UTF-8)-encoded chars. For example: ```logsql -log.уровень:exact_prefix("ошиб") +log.уровень:exact("ошиб"*) ``` The field name can be put inside quotes if it contains special chars, which may clash with the query syntax. For example, the following query matches `log:level` values starting with `err` prefix: ```logsql -"log:level":exact_prefix("err") +"log:level":exact("err"*) ``` See also: @@ -809,7 +809,7 @@ Performance tips: Note that the `re("error|warning")` matches `errors` as well as `warnings` [words](#word), while `error OR warning` matches only the specified [words](#word). See also [multi-exact filter](#multi-exact-filter). - Prefer moving the regexp filter to the end of the [logical filter](#logical-filter), so lightweighter filters are executed first. -- Prefer using `exact_prefix("some prefix")` instead of `re("^some prefix")`, since the [exact_prefix()](#exact-prefix-filter) works much faster than the `re()` filter. +- Prefer using `exact("some prefix"*)` instead of `re("^some prefix")`, since the [exact()](#exact-prefix-filter) works much faster than the `re()` filter. - See [other performance tips](#performance-tips). See also: diff --git a/lib/logstorage/filters.go b/lib/logstorage/filters.go index 55a8a99053..9a978ce44f 100644 --- a/lib/logstorage/filters.go +++ b/lib/logstorage/filters.go @@ -481,7 +481,7 @@ func (sf *sequenceFilter) apply(bs *blockSearch, bm *filterBitmap) { // exactPrefixFilter matches the exact prefix. // -// Example LogsQL: `fieldName:exact_prefix("foo bar") +// Example LogsQL: `fieldName:exact("foo bar"*) type exactPrefixFilter struct { fieldName string prefix string @@ -491,7 +491,7 @@ type exactPrefixFilter struct { } func (ef *exactPrefixFilter) String() string { - return fmt.Sprintf("%sexact_prefix(%s)", quoteFieldNameIfNeeded(ef.fieldName), quoteTokenIfNeeded(ef.prefix)) + return fmt.Sprintf("%sexact(%s*)", quoteFieldNameIfNeeded(ef.fieldName), quoteTokenIfNeeded(ef.prefix)) } func (ef *exactPrefixFilter) getTokens() []string { diff --git a/lib/logstorage/parser.go b/lib/logstorage/parser.go index 9768939d26..c4c6fc0885 100644 --- a/lib/logstorage/parser.go +++ b/lib/logstorage/parser.go @@ -320,8 +320,6 @@ func parseGenericFilter(lex *lexer, fieldName string) (filter, error) { return parseNotFilter(lex, fieldName) case lex.isKeyword("exact"): return parseExactFilter(lex, fieldName) - case lex.isKeyword("exact_prefix"): - return parseExactPrefixFilter(lex, fieldName) case lex.isKeyword("i"): return parseAnyCaseFilter(lex, fieldName) case lex.isKeyword("in"): @@ -474,6 +472,23 @@ func parseNotFilter(lex *lexer, fieldName string) (filter, error) { } func parseAnyCaseFilter(lex *lexer, fieldName string) (filter, error) { + return parseFuncArgMaybePrefix(lex, "i", fieldName, func(phrase string, isPrefixFilter bool) (filter, error) { + if isPrefixFilter { + f := &anyCasePrefixFilter{ + fieldName: fieldName, + prefix: phrase, + } + return f, nil + } + f := &anyCasePhraseFilter{ + fieldName: fieldName, + phrase: phrase, + } + return f, nil + }) +} + +func parseFuncArgMaybePrefix(lex *lexer, funcName, fieldName string, f func(arg string, isPrefiFilter bool) (filter, error)) (filter, error) { phrase := lex.token lex.nextToken() if !lex.isKeyword("(") { @@ -481,33 +496,21 @@ func parseAnyCaseFilter(lex *lexer, fieldName string) (filter, error) { return parseFilterForPhrase(lex, phrase, fieldName) } if !lex.mustNextToken() { - return nil, fmt.Errorf("missing arg for i()") + return nil, fmt.Errorf("missing arg for %s()", funcName) } phrase = getCompoundFuncArg(lex) isPrefixFilter := false if lex.isKeyword("*") && !lex.isSkippedSpace { isPrefixFilter = true if !lex.mustNextToken() { - return nil, fmt.Errorf("missing ')' after i()") + return nil, fmt.Errorf("missing ')' after %s()", funcName) } } if !lex.isKeyword(")") { - return nil, fmt.Errorf("unexpected token %q instead of ')' in i()", lex.token) + return nil, fmt.Errorf("unexpected token %q instead of ')' in %s()", lex.token, funcName) } lex.nextToken() - - if isPrefixFilter { - f := &anyCasePrefixFilter{ - fieldName: fieldName, - prefix: phrase, - } - return f, nil - } - f := &anyCasePhraseFilter{ - fieldName: fieldName, - phrase: phrase, - } - return f, nil + return f(phrase, isPrefixFilter) } func parseLenRangeFilter(lex *lexer, fieldName string) (filter, error) { @@ -624,22 +627,19 @@ func parseSequenceFilter(lex *lexer, fieldName string) (filter, error) { } func parseExactFilter(lex *lexer, fieldName string) (filter, error) { - return parseFuncArg(lex, fieldName, func(arg string) (filter, error) { - ef := &exactFilter{ - fieldName: fieldName, - value: arg, + return parseFuncArgMaybePrefix(lex, "exact", fieldName, func(phrase string, isPrefixFilter bool) (filter, error) { + if isPrefixFilter { + f := &exactPrefixFilter{ + fieldName: fieldName, + prefix: phrase, + } + return f, nil } - return ef, nil - }) -} - -func parseExactPrefixFilter(lex *lexer, fieldName string) (filter, error) { - return parseFuncArg(lex, fieldName, func(arg string) (filter, error) { - ef := &exactPrefixFilter{ + f := &exactFilter{ fieldName: fieldName, - prefix: arg, + value: phrase, } - return ef, nil + return f, nil }) } @@ -1082,7 +1082,6 @@ var reservedKeywords = func() map[string]struct{} { // functions "exact", - "exact_prefix", "i", "in", "ipv4_range", diff --git a/lib/logstorage/parser_test.go b/lib/logstorage/parser_test.go index 90769479e3..d1aeb058e4 100644 --- a/lib/logstorage/parser_test.go +++ b/lib/logstorage/parser_test.go @@ -627,12 +627,6 @@ func TestParseQuerySuccess(t *testing.T) { f("a:exact", `a:"exact"`) f("a:exact-foo", `a:exact-foo`) f("exact-foo:b", `exact-foo:b`) - f("exact_prefix", `"exact_prefix"`) - f("exact_prefix:a", `"exact_prefix":a`) - f("exact_prefix-foo", `exact_prefix-foo`) - f("a:exact_prefix", `a:"exact_prefix"`) - f("a:exact_prefix-foo", `a:exact_prefix-foo`) - f("exact_prefix-foo:b", `exact_prefix-foo:b`) f("i", `"i"`) f("i-foo", `i-foo`) f("a:i-foo", `a:i-foo`) @@ -676,17 +670,11 @@ func TestParseQuerySuccess(t *testing.T) { // exact filter f("exact(foo)", `exact(foo)`) + f("exact(foo*)", `exact(foo*)`) f("exact('foo bar),|baz')", `exact("foo bar),|baz")`) - f(`exact(foo-bar,)`, `exact(foo-bar)`) + f("exact('foo bar),|baz'*)", `exact("foo bar),|baz"*)`) f(`exact(foo|b:ar)`, `exact("foo|b:ar")`) - f(`foo:exact(f,)`, `foo:exact(f)`) - - // exact_prefix filter - f("exact_prefix(foo)", `exact_prefix(foo)`) - f(`exact_prefix("foo bar")`, `exact_prefix("foo bar")`) - f(`exact_prefix(foo-bar,)`, `exact_prefix(foo-bar)`) - f(`exact_prefix(foo|b:ar)`, `exact_prefix("foo|b:ar")`) - f(`foo:exact_prefix(f,)`, `foo:exact_prefix(f)`) + f(`foo:exact(foo|b:ar*)`, `foo:exact("foo|b:ar"*)`) // i filter f("i(foo)", `i(foo)`) @@ -877,9 +865,9 @@ func TestParseQueryFailure(t *testing.T) { f(`exact(f, b)`) f(`exact(foo`) f(`exact(foo,`) - f(`exact(foo*)`) f(`exact(foo bar)`) f(`exact(foo, bar`) + f(`exact(foo,)`) // invalid i f(`i(`)