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

Generic Event String Selector #2087

Merged
merged 2 commits into from
Jul 28, 2016
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
5 changes: 5 additions & 0 deletions libbeat/common/fmtstr/formatevents.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ func (fs *EventFormatString) Eval(out *bytes.Buffer, event common.MapStr) error
return fs.formatter.Eval(ctx, out)
}

// IsConst checks the format string always returning the same constant string
func (fs *EventFormatString) IsConst() bool {
return fs.formatter.IsConst()
}

// collectFields tries to extract and convert all required fields into an array
// of strings.
func (fs *EventFormatString) collectFields(
Expand Down
1 change: 1 addition & 0 deletions libbeat/outputs/outil/outil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package outil
302 changes: 302 additions & 0 deletions libbeat/outputs/outil/select.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
package outil

import (
"fmt"

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

type Selector struct {
sel selector
}

type Settings struct {
// single selector key and default option keyword
Key string

// multi-selector key in config
MultiKey string

// if enabled a selector `key` in config will be generated, if `key` is present
EnableSingleOnly bool

// Fail building selector if `key` and `multiKey` are missing
FailEmpty bool
}

type selector interface {
sel(evt common.MapStr) (string, error)
}

type emptySelector struct{}

type listSelector struct {
selectors []selector
}

type condSelector struct {
s selector
cond *processors.Condition
}

type constSelector struct {
s string
}

type fmtSelector struct {
f fmtstr.EventFormatString
otherwise string
}

type mapSelector struct {
from selector
otherwise string
to map[string]string
}

var nilSelector selector = &emptySelector{}

// Select runs configured selector against the current event.
// If no matching selector is found, an empty string is returned.
// It's up to the caller to decide if an empty string is an error
// or an expected result.
func (s Selector) Select(evt common.MapStr) (string, error) {
return s.sel.sel(evt)
}

func BuildSelector(cfg *common.Config, settings Settings) (Selector, error) {
var sel []selector

key := settings.Key
multiKey := settings.MultiKey
found := false

if cfg.HasField(multiKey) {
found = true
sub, err := cfg.Child(multiKey, -1)
if err != nil {
return Selector{}, err
}

var table []*common.Config
if err := sub.Unpack(&table); err != nil {
return Selector{}, err
}

for _, config := range table {
action, err := buildSingle(config, key)
if err != nil {
return Selector{}, err
}

if action != nilSelector {
sel = append(sel, action)
}
}
}

if settings.EnableSingleOnly && cfg.HasField(key) {
found = true

// expect event-format-string
str, err := cfg.String(key, -1)
if err != nil {
return Selector{}, err
}

fmtstr, err := fmtstr.CompileEvent(str)
if err != nil {
return Selector{}, fmt.Errorf("%v in %v", err, cfg.PathOf(key))
}

sel = append(sel, &fmtSelector{f: *fmtstr})
}

if settings.FailEmpty && !found {
if settings.EnableSingleOnly {
return Selector{}, fmt.Errorf("missing required '%v' or '%v' in %v",
key, multiKey, cfg.Path())
}

return Selector{}, fmt.Errorf("missing required '%v' in %v",
multiKey, cfg.Path())
}

switch len(sel) {
case 0:
return Selector{nilSelector}, nil
case 1:
return Selector{sel[0]}, nil
default:
return Selector{&listSelector{sel}}, nil
}
}

func buildSingle(cfg *common.Config, key string) (selector, error) {
// TODO: check for unknown fields

// 3. extract required key-word handler
Copy link
Contributor

Choose a reason for hiding this comment

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

what's the numbering here? Is this supposed to be 1.?

if !cfg.HasField(key) {
return nil, fmt.Errorf("missing %v", cfg.PathOf(key))
}

str, err := cfg.String(key, -1)
if err != nil {
return nil, err
}

evtfmt, err := fmtstr.CompileEvent(str)
if err != nil {
return nil, fmt.Errorf("%v in %v", err, cfg.PathOf(key))
}

// 2. extract optional `default` value
var otherwise string
if cfg.HasField("default") {
tmp, err := cfg.String("default", -1)
if err != nil {
return nil, err
}
otherwise = tmp
}

// 3. extract optional `mapping`
mapping := struct {
Table map[string]string `config:"mappings"`
}{nil}
if cfg.HasField("mappings") {
if err := cfg.Unpack(&mapping); err != nil {
return nil, err
}
}

// 4. extract conditional
var cond *processors.Condition
if cfg.HasField("when") {
sub, err := cfg.Child("when", -1)
if err != nil {
return nil, err
}

condConfig := processors.ConditionConfig{}
if err := sub.Unpack(&condConfig); err != nil {
return nil, err
}

tmp, err := processors.NewCondition(&condConfig)
if err != nil {
return nil, err
}

cond = tmp
}

// 5. build selector from available fields
var sel selector
if len(mapping.Table) > 0 {
if evtfmt.IsConst() {
str, err := evtfmt.Run(common.MapStr{})
if err != nil {
return nil, err
}

str = mapping.Table[str]
if str == "" {
str = otherwise
}

if str == "" {
sel = nilSelector
} else {
sel = &constSelector{str}
}
} else {
sel = &mapSelector{
from: &fmtSelector{f: *evtfmt},
to: mapping.Table,
otherwise: otherwise,
}
}
} else {
if evtfmt.IsConst() {
str, err := evtfmt.Run(common.MapStr{})
if err != nil {
return nil, err
}
sel = &constSelector{str}
} else {
sel = &fmtSelector{f: *evtfmt, otherwise: otherwise}
}
}
if cond != nil && sel != nilSelector {
sel = &condSelector{s: sel, cond: cond}
}

return sel, nil
}

func (s *emptySelector) sel(evt common.MapStr) (string, error) {
return "", nil
}

func (s *listSelector) sel(evt common.MapStr) (string, error) {
for _, sub := range s.selectors {
n, err := sub.sel(evt)
if err != nil { // TODO: try
return n, err
}

if n != "" {
return n, nil
}
}

return "", nil
}

func (s *condSelector) sel(evt common.MapStr) (string, error) {
if !s.cond.Check(evt) {
return "", nil
}
return s.s.sel(evt)
}

func (s *constSelector) sel(_ common.MapStr) (string, error) {
return s.s, nil
}

func (s *fmtSelector) sel(evt common.MapStr) (string, error) {
n, err := s.f.Run(evt)
if err != nil {
// err will be set if not all keys present in event ->
// return empty selector result and ignore error
return s.otherwise, nil
}

if n == "" {
return s.otherwise, nil
}
return n, nil
}

func (s *mapSelector) sel(evt common.MapStr) (string, error) {
n, err := s.from.sel(evt)
if err != nil {
if s.otherwise == "" {
return "", err
}
return s.otherwise, nil
}

if n == "" {
return s.otherwise, nil
}

n = s.to[n]
if n == "" {
return s.otherwise, nil
}
return n, nil
}
Loading