Skip to content

Commit

Permalink
Merge pull request #506 from magnusbaeck/fields
Browse files Browse the repository at this point in the history
Filebeat: Support array and dictionary fields
  • Loading branch information
tsg committed Dec 15, 2015
2 parents 841f26b + 6435194 commit 7ec8245
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 5 deletions.
3 changes: 2 additions & 1 deletion filebeat/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

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

Expand Down Expand Up @@ -52,7 +53,7 @@ type ProspectorConfig struct {

type HarvesterConfig struct {
InputType string `yaml:"input_type"`
Fields map[string]string
Fields common.MapStr
FieldsUnderRoot bool `yaml:"fields_under_root"`
BufferSize int `yaml:"harvester_buffer_size"`
TailFiles bool `yaml:"tail_files"`
Expand Down
2 changes: 1 addition & 1 deletion filebeat/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestReadConfig(t *testing.T) {
assert.Equal(t, "/var/log/s*.log", prospectors[0].Paths[1])
assert.Equal(t, "log", prospectors[0].Input)
assert.Equal(t, 3, len(prospectors[0].Harvester.Fields))
assert.Equal(t, 1, len(prospectors[0].Harvester.Fields["review"]))
assert.Equal(t, "1", prospectors[0].Harvester.Fields["review"])
assert.Equal(t, "24h", prospectors[0].IgnoreOlder)
assert.Equal(t, "10s", prospectors[0].ScanFrequency)

Expand Down
3 changes: 2 additions & 1 deletion filebeat/docs/configuration.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ If both `include_lines` and `exclude_lines` are defined, then include_lines is c
===== fields

Optional fields that you can specify to add additional information to the output. For
example, you might add fields that you can use for filtering log data. By default,
example, you might add fields that you can use for filtering log data. Fields can be
scalar values, arrays, dictionaries, or any nested combination of these. All scalar values will be interpreted as strings. By default,
the fields that you specify here will be grouped under a `fields` sub-dictionary in the output document. To store the custom fields as top-level fields, set the `fields_under_root` option to true.

[source,yaml]
Expand Down
2 changes: 1 addition & 1 deletion filebeat/input/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type FileEvent struct {
Offset int64
Bytes int
Text *string
Fields *map[string]string
Fields *common.MapStr
Fileinfo *os.FileInfo

fieldsUnderRoot bool
Expand Down
3 changes: 2 additions & 1 deletion filebeat/input/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path/filepath"
"testing"

"github.com/elastic/beats/libbeat/common"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -114,7 +115,7 @@ func TestFileEventToMapStr(t *testing.T) {

func TestFieldsUnderRoot(t *testing.T) {
event := FileEvent{
Fields: &map[string]string{
Fields: &common.MapStr{
"hello": "world",
},
}
Expand Down
49 changes: 49 additions & 0 deletions libbeat/common/mapstr.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,52 @@ func (m MapStr) String() string {
}
return string(bytes)
}

// UnmarshalYAML helps out with the YAML unmarshalling when the target
// variable is a MapStr. The default behavior is to unmarshal nested
// maps to map[interface{}]interface{} values, and such values can't
// be marshalled as JSON.
//
// The keys of map[interface{}]interface{} maps will be converted to
// strings with a %v format string, as will any scalar values that
// aren't already strings (i.e. numbers and boolean values).
//
// Since we want to modify the receiver it needs to be a pointer.
func (ms *MapStr) UnmarshalYAML(unmarshal func(interface{}) error) error {
var result map[interface{}]interface{}
err := unmarshal(&result)
if err != nil {
panic(err)
}
*ms = cleanUpInterfaceMap(result)
return nil
}

func cleanUpInterfaceArray(in []interface{}) []interface{} {
result := make([]interface{}, len(in))
for i, v := range in {
result[i] = cleanUpMapValue(v)
}
return result
}

func cleanUpInterfaceMap(in map[interface{}]interface{}) MapStr {
result := make(MapStr)
for k, v := range in {
result[fmt.Sprintf("%v", k)] = cleanUpMapValue(v)
}
return result
}

func cleanUpMapValue(v interface{}) interface{} {
switch v := v.(type) {
case []interface{}:
return cleanUpInterfaceArray(v)
case map[interface{}]interface{}:
return cleanUpInterfaceMap(v)
case string:
return v
default:
return fmt.Sprintf("%v", v)
}
}
83 changes: 83 additions & 0 deletions libbeat/common/mapstr_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package common

import (
"strings"
"testing"
"time"

"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v2"
)

func TestMapStrUpdate(t *testing.T) {
Expand Down Expand Up @@ -185,3 +187,84 @@ func TestString(t *testing.T) {
assert.Equal(t, test.Output, test.Input.String())
}
}

func TestUnmarshalYAML(t *testing.T) {
type io struct {
InputLines []string
Output MapStr
}
tests := []io{
// should return nil for empty document
{
InputLines: []string{},
Output: nil,
},
// should handle scalar values
{
InputLines: []string{
"a: b",
"c: true",
"123: 456",
},
Output: MapStr{
"a": "b",
"c": "true",
"123": "456",
},
},
// should handle array with scalar values
{
InputLines: []string{
"a:",
" - b",
" - true",
" - 123",
},
Output: MapStr{
"a": []interface{}{"b", "true", "123"},
},
},
// should handle array with nested map
{
InputLines: []string{
"a:",
" - b: c",
" d: true",
" 123: 456",
},
Output: MapStr{
"a": []interface{}{
MapStr{
"b": "c",
"d": "true",
"123": "456",
},
},
},
},
// should handle nested map
{
InputLines: []string{
"a: ",
" b: c",
" d: true",
" 123: 456",
},
Output: MapStr{
"a": MapStr{
"b": "c",
"d": "true",
"123": "456",
},
},
},
}
for _, test := range tests {
var actual MapStr
if err := yaml.Unmarshal([]byte(strings.Join(test.InputLines, "\n")), &actual); err != nil {
assert.Fail(t, "YAML unmarshaling unexpectedly failed: %s", err)
continue
}
assert.Equal(t, test.Output, actual)
}
}

0 comments on commit 7ec8245

Please sign in to comment.