Skip to content
New issue

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

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

Already on GitHub? # to your account

Make @metadata accessible via beat.Event accessor methods #10761

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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-developer.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ The list below covers the major changes between 7.0.0-beta1 and master only.

- Metricset generator generates beta modules by default now. {pull}10657[10657]
- Move host name addition to a processor. {pull}10728[10728]
- The `beat.Event` accessor methods now support `@metadata` keys. {pull}10761[10761]
47 changes: 46 additions & 1 deletion libbeat/beat/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package beat

import (
"errors"
"strings"
"time"

"github.com/elastic/beats/libbeat/common"
Expand All @@ -40,6 +41,7 @@ type Event struct {

var (
errNoTimestamp = errors.New("value is no timestamp")
errNoMapStr = errors.New("value is no map[string]interface{} type")
)

// SetID overwrites the "id" field in the events metadata.
Expand All @@ -54,6 +56,11 @@ func (e *Event) SetID(id string) {
func (e *Event) GetValue(key string) (interface{}, error) {
if key == "@timestamp" {
return e.Timestamp, nil
} else if subKey, ok := metadataKey(key); ok {
if subKey == "" || e.Meta == nil {
return e.Meta, nil
}
return e.Meta.GetValue(subKey)
}
return e.Fields.GetValue(key)
}
Expand All @@ -68,12 +75,50 @@ func (e *Event) PutValue(key string, v interface{}) (interface{}, error) {
default:
return nil, errNoTimestamp
}
} else if subKey, ok := metadataKey(key); ok {
if subKey == "" {
switch meta := v.(type) {
case common.MapStr:
e.Meta = meta
case map[string]interface{}:
e.Meta = meta
default:
return nil, errNoMapStr
}
} else if e.Meta == nil {
e.Meta = common.MapStr{}
}
return e.Meta.Put(subKey, v)
}

// TODO: add support to write into '@metadata'?
return e.Fields.Put(key, v)
}

func (e *Event) Delete(key string) error {
if subKey, ok := metadataKey(key); ok {
if subKey == "" {
e.Meta = nil
return nil
}
if e.Meta == nil {
return nil
}
return e.Meta.Delete(subKey)
}
return e.Fields.Delete(key)
}

func metadataKey(key string) (string, bool) {
if !strings.HasPrefix(key, "@metadata") {
Copy link
Contributor

@kvch kvch Feb 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if it would be safer to check for "@metadata."? What if an event includes the key "@metadata2.keyname and wants to delete @metadata2? If I understand correctly so far, it means that Meta gets deleted.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a test case to show that Delete(@metadataSpecial) doesn't cause Meta to be deleted.

The reason it checks for @metadata is to allow for operations like Put(@metadata, map[string]interface{}{x: y}) and Get(@metadata). When it finds that @metadata as the prefix it then checks to see if the next character is a . so in effect it is checking for @metadata..

return "", false
}

subKey := key[len("@metadata"):]
if subKey == "" {
return "", true
}
if subKey[0] == '.' {
return subKey[1:], true
}
return "", false
}
129 changes: 129 additions & 0 deletions libbeat/beat/event_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package beat

import (
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/elastic/beats/libbeat/common"
)

func newEmptyEvent() *Event {
return &Event{Fields: common.MapStr{}}
}

func TestEventPutGetTimestamp(t *testing.T) {
evt := newEmptyEvent()
ts := time.Now()

evt.PutValue("@timestamp", ts)

v, err := evt.GetValue("@timestamp")
if err != nil {
t.Fatal(err)
}

assert.Equal(t, ts, v)
assert.Equal(t, ts, evt.Timestamp)

// The @timestamp is also written into Fields.
assert.Equal(t, ts, evt.Fields["@timestamp"])
}

func TestEventMetadata(t *testing.T) {
const id = "123"
newMeta := func() common.MapStr { return common.MapStr{"id": id} }

t.Run("put", func(t *testing.T) {
evt := newEmptyEvent()
meta := newMeta()

evt.PutValue("@metadata", meta)

assert.Equal(t, meta, evt.Meta)
assert.Empty(t, evt.Fields)
})

t.Run("get", func(t *testing.T) {
evt := newEmptyEvent()
evt.Meta = newMeta()

meta, err := evt.GetValue("@metadata")

assert.NoError(t, err)
assert.Equal(t, evt.Meta, meta)
})

t.Run("put sub-key", func(t *testing.T) {
evt := newEmptyEvent()

evt.PutValue("@metadata.id", id)

assert.Equal(t, newMeta(), evt.Meta)
assert.Empty(t, evt.Fields)
})

t.Run("get sub-key", func(t *testing.T) {
evt := newEmptyEvent()
evt.Meta = newMeta()

v, err := evt.GetValue("@metadata.id")

assert.NoError(t, err)
assert.Equal(t, id, v)
})

t.Run("delete", func(t *testing.T) {
evt := newEmptyEvent()
evt.Meta = newMeta()

err := evt.Delete("@metadata")

assert.NoError(t, err)
assert.Nil(t, evt.Meta)
})

t.Run("delete sub-key", func(t *testing.T) {
evt := newEmptyEvent()
evt.Meta = newMeta()

err := evt.Delete("@metadata.id")

assert.NoError(t, err)
assert.Empty(t, evt.Meta)
})

t.Run("setID", func(t *testing.T) {
evt := newEmptyEvent()

evt.SetID(id)

assert.Equal(t, newMeta(), evt.Meta)
})

t.Run("put non-metadata", func(t *testing.T) {
evt := newEmptyEvent()

evt.PutValue("@metadataSpecial", id)

assert.Equal(t, common.MapStr{"@metadataSpecial": id}, evt.Fields)
})
}