Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Journald] update include_matches #42517

Merged
merged 8 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ otherwise no tag is added. {issue}42208[42208] {pull}42403[42403]
- Introduce ignore older and start timestamp filters for AWS S3 input. {pull}41804[41804]
- Journald input now can report its status to Elastic-Agent {issue}39791[39791] {pull}42462[42462]
- Publish events progressively in the Okta provider of the Entity Analytics input. {issue}40106[40106] {pull}42567[42567]
- Journald `include_matches.match` now accepts `+` to represent a logical disjunction (OR) {issue}40185[40185] {pull}42517[42517]
- The journald input is now generally available. {pull}42107[42107]
- Add metrics for number of events and pages published by HTTPJSON input. {issue}42340[42340] {pull}42442[42442]

Expand Down
12 changes: 10 additions & 2 deletions filebeat/_meta/config/filebeat.inputs.reference.yml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -810,11 +810,19 @@ filebeat.inputs:
#- 2

# You may wish to have separate inputs for each service. You can use
# include_matches.or to specify a list of filter expressions that are
# applied as a logical OR.
# include_matches.match to define a list of filters, each event needs
# to match all filters defined.
#include_matches.match:
#- _SYSTEMD_UNIT=foo.service

# To create a disjunction (logical OR) use the `+` character between
# the filters
#include_matches:
#match:
#- "systemd.transport=kernel"
#- "+"
#- "journald.process.name=systemd"

# Uses the original hostname of the entry instead of the one
# from the host running jounrald
#save_remote_hostname: false
Expand Down
31 changes: 30 additions & 1 deletion filebeat/docs/inputs/input-journald.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,13 @@ numeric code.
==== `include_matches`

A collection of filter expressions used to match fields. The format of the expression
is `field=value`. {beatname_uc} fetches all events that exactly match the
is `field=value` or `+` representing disjunction (i.e. logical
OR). {beatname_uc} fetches all events that exactly match the
expressions. Pattern matching is not supported.

When `+` is used, it will cause all matches before and after to be
combined in a disjunction (i.e. logical OR).

If you configured a filter expression, only entries with this field set will be iterated by the journald reader of Filebeat.
If the filter expressions apply to different fields, only entries with all fields set will be iterated.
If they apply to the same fields, only entries where the field takes one of the specified values will be iterated.
Expand Down Expand Up @@ -213,6 +217,31 @@ include_matches:
- "systemd.transport=syslog"
----


The following include matches configuration is the equivalent of the
following logical expression:

```
A=a OR (B=b AND C=c) OR (D=d AND B=1)
```

["source","yaml",subs="attributes"]
----
include_matches:
match:
- A=a
- +
- B=b
- C=c
- +
- B=1
----

`include_matches` translates to `journalctl` `MATCHES`, its
[documentation](https://www.man7.org/linux/man-pages/man1/journalctl.1.html)
is not clear about how multiple disjunctions are handled. The previous
example was tested with journalctl version 257.

To reference fields, use one of the following:

* The field name used by the systemd journal. For example,
Expand Down
12 changes: 10 additions & 2 deletions filebeat/filebeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1223,11 +1223,19 @@ filebeat.inputs:
#- 2

# You may wish to have separate inputs for each service. You can use
# include_matches.or to specify a list of filter expressions that are
# applied as a logical OR.
# include_matches.match to define a list of filters, each event needs
# to match all filters defined.
#include_matches.match:
#- _SYSTEMD_UNIT=foo.service

# To create a disjunction (logical OR) use the `+` character between
# the filters
#include_matches:
#match:
#- "systemd.transport=kernel"
#- "+"
#- "journald.process.name=systemd"

# Uses the original hostname of the entry instead of the one
# from the host running jounrald
#save_remote_hostname: false
Expand Down
35 changes: 30 additions & 5 deletions filebeat/input/journald/pkg/journalfield/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,50 @@ type MatcherBuilder struct {
// IncludeMatches stores the advanced matching configuratio
// provided by the user.
type IncludeMatches struct {
Matches []Matcher `config:"match"`
AND []IncludeMatches `config:"and"`
OR []IncludeMatches `config:"or"`
Matches []Matcher `config:"match"`
}

var (
defaultBuilder = MatcherBuilder{Conversions: journaldEventFields}
)

func (i IncludeMatches) Validate() error {
if len(i.AND) != 0 || len(i.OR) != 0 {
return errors.New("'or' and 'and' are not supported at the moment")
var errs []error
for _, m := range i.Matches {
if err := m.validate(); err != nil {
errs = append(errs, err)
}
}

return errors.Join(errs...)
}

var errInvalidMatcher = errors.New("expression must be '+' or in the format 'field=value'")

func (m Matcher) validate() error {
if len(m.str) == 1 {
if m.str != "+" {
return fmt.Errorf("'%s' is invalid, %w", m.str, errInvalidMatcher)
}

return nil
}

elems := strings.Split(m.str, "=")
if len(elems) != 2 {
return fmt.Errorf("'%s' is invalid, %w", m.str, errInvalidMatcher)
}

return nil
}

// Build creates a new Matcher using the configured conversion table.
// If no table has been configured the internal default table will be used.
func (b MatcherBuilder) Build(in string) (Matcher, error) {
if in == "+" {
return Matcher{in}, nil
}

elems := strings.Split(in, "=")
if len(elems) != 2 {
return Matcher{}, fmt.Errorf("invalid match format: %s", in)
Expand Down
24 changes: 4 additions & 20 deletions filebeat/input/journald/pkg/journalfield/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,6 @@ func TestValidate(t *testing.T) {
im IncludeMatches
error bool
}{
{
name: "OR condition exists",
im: IncludeMatches{
OR: []IncludeMatches{
{},
},
},
error: true,
},
{
name: "AND condition exists",
im: IncludeMatches{
AND: []IncludeMatches{
{},
},
},
error: true,
},
{
name: "empty include matches succeeds validation",
im: IncludeMatches{},
Expand All @@ -53,8 +35,10 @@ func TestValidate(t *testing.T) {
name: "matches are allowed",
im: IncludeMatches{
Matches: []Matcher{
{"foo"},
{"bar"},
{"foo=bar"},
{"+"},
{"FOO=bar"},
{"foo.bar=foo"},
},
},
},
Expand Down
12 changes: 10 additions & 2 deletions x-pack/filebeat/filebeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2907,11 +2907,19 @@ filebeat.inputs:
#- 2

# You may wish to have separate inputs for each service. You can use
# include_matches.or to specify a list of filter expressions that are
# applied as a logical OR.
# include_matches.match to define a list of filters, each event needs
# to match all filters defined.
#include_matches.match:
#- _SYSTEMD_UNIT=foo.service

# To create a disjunction (logical OR) use the `+` character between
# the filters
#include_matches:
#match:
#- "systemd.transport=kernel"
#- "+"
#- "journald.process.name=systemd"

# Uses the original hostname of the entry instead of the one
# from the host running jounrald
#save_remote_hostname: false
Expand Down
Loading