-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Journalbeat matches support && minor additions #8324
Changes from 7 commits
ee766e1
79c2ae3
33f7fb7
df02467
6347351
b99005e
3c2f283
cc90e1e
2a8d113
8a916d8
1f50262
4401d9b
80aa2e4
f23a8a1
ddf8598
ad7ddcd
528cba7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
FROM golang:1.10.3 | ||
MAINTAINER Noémi Ványi <[email protected]> | ||
|
||
RUN set -x && \ | ||
apt-get update && \ | ||
apt-get install -y --no-install-recommends \ | ||
python-pip virtualenv libsystemd-dev libc6-dev-i386 gcc-arm-linux-gnueabi && \ | ||
apt-get clean | ||
|
||
RUN pip install --upgrade setuptools | ||
|
||
# Setup work environment | ||
ENV JOURNALBEAT_PATH /go/src/github.com/elastic/beats/journalbeat | ||
|
||
RUN mkdir -p $JOURNALBEAT_PATH/build/coverage | ||
WORKDIR $JOURNALBEAT_PATH | ||
HEALTHCHECK CMD exit 0 |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,8 +18,10 @@ | |
package reader | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
"strings" | ||
"time" | ||
|
||
"github.com/coreos/go-systemd/sdjournal" | ||
|
@@ -50,6 +52,8 @@ type Config struct { | |
Backoff time.Duration | ||
// BackoffFactor is the multiplier of Backoff. | ||
BackoffFactor int | ||
// Matches store the key value pairs to match entries. | ||
Matches []string | ||
} | ||
|
||
// Reader reads entries from journal(s). | ||
|
@@ -83,6 +87,11 @@ func New(c Config, done chan struct{}, state checkpoint.JournalState, logger *lo | |
} | ||
} | ||
|
||
err = setupMatches(j, c.Matches) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
r := &Reader{ | ||
journal: j, | ||
changes: make(chan int), | ||
|
@@ -108,6 +117,11 @@ func NewLocal(c Config, done chan struct{}, state checkpoint.JournalState, logge | |
logger = logger.With("path", "local") | ||
logger.Debug("New local journal is opened for reading") | ||
|
||
err = setupMatches(j, c.Matches) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
r := &Reader{ | ||
journal: j, | ||
changes: make(chan int), | ||
|
@@ -119,6 +133,39 @@ func NewLocal(c Config, done chan struct{}, state checkpoint.JournalState, logge | |
return r, nil | ||
} | ||
|
||
func setupMatches(j *sdjournal.Journal, matches []string) error { | ||
for _, m := range matches { | ||
elems := strings.Split(m, "=") | ||
if len(elems) != 2 { | ||
return fmt.Errorf("invalid match format: %s", m) | ||
} | ||
|
||
var p string | ||
for journalKey, eventKey := range journaldEventFields { | ||
if elems[0] == eventKey { | ||
p = journalKey + "=" + elems[1] | ||
} | ||
} | ||
|
||
if p == "" { | ||
return fmt.Errorf("cannot create matcher: invalid event key: %s", elems[0]) | ||
} | ||
|
||
logp.Debug("journal", "Added matcher expression: %s", p) | ||
|
||
err := j.AddMatch(p) | ||
if err != nil { | ||
return fmt.Errorf("error adding match to journal %v", err) | ||
} | ||
|
||
err = j.AddDisjunction() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh this is an interesting API, this is to add a logical |
||
if err != nil { | ||
return fmt.Errorf("error adding disjunction to journal: %v", err) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unit test? |
||
// seek seeks to the position determined by the coniguration and cursor state. | ||
func (r *Reader) seek(cursor string) { | ||
if r.config.Seek == "cursor" { | ||
|
@@ -213,21 +260,33 @@ func (r *Reader) readUntilNotNull(entries chan<- *beat.Event) error { | |
// toEvent creates a beat.Event from journal entries. | ||
func (r *Reader) toEvent(entry *sdjournal.JournalEntry) *beat.Event { | ||
fields := common.MapStr{} | ||
for journalKey, eventKey := range journaldEventFields { | ||
if entry.Fields[journalKey] != "" { | ||
fields.Put(eventKey, entry.Fields[journalKey]) | ||
custom := common.MapStr{} | ||
|
||
for k, v := range entry.Fields { | ||
if kk, _ := journaldEventFields[k]; kk == "" { | ||
normalized := strings.ToLower(strings.TrimLeft(k, "_")) | ||
custom.Put(normalized, v) | ||
} else { | ||
fields.Put(kk, v) | ||
} | ||
} | ||
|
||
if len(custom) != 0 { | ||
fields["custom"] = custom | ||
} | ||
|
||
state := checkpoint.JournalState{ | ||
Path: r.config.Path, | ||
Cursor: entry.Cursor, | ||
RealtimeTimestamp: entry.RealtimeTimestamp, | ||
MonotonicTimestamp: entry.MonotonicTimestamp, | ||
} | ||
|
||
fields["read_timestamp"] = time.Now() | ||
receivedByJournal := time.Unix(0, int64(entry.RealtimeTimestamp)*1000) | ||
|
||
event := beat.Event{ | ||
Timestamp: time.Now(), | ||
Timestamp: receivedByJournal, | ||
Fields: fields, | ||
Private: state, | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -109,6 +109,39 @@ def test_read_events_with_existing_registry(self): | |
exit_code = journalbeat_proc.kill_and_wait() | ||
assert exit_code == 0 | ||
|
||
@unittest.skipUnless(sys.platform.startswith("linux"), "Journald only on Linux") | ||
def test_read_events_with_existing_registry(self): | ||
""" | ||
Journalbeat is able to pass matchers to the journal reader and read filtered messages. | ||
""" | ||
|
||
self.render_config_template( | ||
journal_path=self.beat_path + "/tests/system/input/test.journal", | ||
seek_method="head", | ||
matches="syslog.priority=5", | ||
path=os.path.abspath(self.working_dir) + "/log/*", | ||
) | ||
journalbeat_proc = self.start_beat() | ||
|
||
required_log_snippets = [ | ||
# journalbeat can be started | ||
"journalbeat is running", | ||
# journalbeat can seek to the position defined in the cursor | ||
"Added matcher expression", | ||
# message can be read from test journal | ||
"unhandled HKEY event 0x60b0", | ||
"please report the conditions when this event happened to", | ||
"unhandled HKEY event 0x60b1", | ||
# Four events with priority 5 is publised | ||
"journalbeat successfully published 4 events", | ||
] | ||
for snippet in required_log_snippets: | ||
self.wait_until(lambda: self.log_contains(snippet), | ||
name="Line in '{}' Journalbeat log".format(snippet)) | ||
|
||
exit_code = journalbeat_proc.kill_and_wait() | ||
assert exit_code == 0 | ||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the integration test! |
||
if __name__ == '__main__': | ||
unittest.main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this docker file will become useful but I wonder if it is used in the tests or if it was missing before?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was missing before. I has every dependency installed, so everyone can build and run
journalbeat
.