processors/dissect/field.go (230 lines of code) (raw):

// 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 dissect import ( "fmt" "strconv" "strings" ) type field interface { MarkGreedy() IsGreedy() bool Ordinal() int Length() int Key() string DataType() string ID() int Apply(b string, m Map) String() string IsSaveable() bool IsFixedLength() bool } type baseField struct { id int key string ordinal int length int greedy bool dataType string } type dataType uint8 // List of dataTypes. const ( Integer dataType = iota Long Float Double String Boolean IP ) var dataTypeNames = map[string]dataType{ "integer": Integer, "long": Long, "float": Float, "double": Double, "string": String, "boolean": Boolean, "ip": IP, } func (f baseField) IsGreedy() bool { return f.greedy } func (f baseField) MarkGreedy() { f.greedy = true //nolint:staticcheck // it is displayed in the logs } func (f baseField) Ordinal() int { return f.ordinal } func (f baseField) Length() int { return f.length } func (f baseField) Key() string { return f.key } func (f baseField) DataType() string { return f.dataType } func (f baseField) ID() int { return f.id } func (f baseField) IsSaveable() bool { return true } func (f baseField) IsFixedLength() bool { return f.length > 0 } func (f baseField) String() string { return fmt.Sprintf("field: %s, ordinal: %d, greedy: %v, dataType: %s", f.key, f.ordinal, f.IsGreedy(), f.DataType()) } // normalField is a simple key reference like this: `%{key}` // // dissect: %{key} // message: hello // result: // key: hello type normalField struct { baseField } func (f normalField) Apply(b string, m Map) { m[f.Key()] = b } // skipField is an skip field without a name like this: `%{}`, this is often used to // skip uninteresting parts of a string. // // dissect: %{} %{key} // message: hello world // result: // key: world type skipField struct { baseField } func (f skipField) Apply(b string, m Map) { } func (f skipField) IsSaveable() bool { return false } // namedSkipFields is a named skip field with the following syntax: `%{?key}`, this is used // in conjunction of the indirect field to create a custom `key => value` pair. // // dissect: %{?key} %{&key} // message: hello world // result: // hello: world // // Deprecated: see pointerField type namedSkipField struct { baseField } func (f namedSkipField) Apply(b string, m Map) { m[f.Key()] = b } func (f namedSkipField) IsSaveable() bool { return false } // pointerField will extract the content between the delimiters and we can reference it during when // extracing other values. type pointerField struct { baseField } func (f pointerField) Apply(b string, m Map) { m[f.Key()] = b } func (f pointerField) IsSaveable() bool { return false } // IndirectField is a value that will be extracted and saved in a previously defined namedSkipField. // the field is defined with the following syntax: `%{&key}`. // // dissect: %{?key} %{&key} // message: hello world // result: // hello: world type indirectField struct { baseField } func (f indirectField) Apply(b string, m Map) { v, ok := m[f.Key()] if ok { m[v] = b return } } // appendField allow an extracted field to be append to a previously extracted values. // the field is defined with the following syntax: `%{+key} %{+key}`. // // dissect: %{+key} %{+key} // message: hello world // result: // key: hello world // // dissect: %{+key/2} %{+key/1} // message: hello world // result: // key: world hello type appendField struct { baseField previous delimiter } func (f appendField) Apply(b string, m Map) { v, ok := m[f.Key()] if ok { m[f.Key()] = v + f.JoinString() + b return } m[f.Key()] = b } func (f appendField) JoinString() string { if f.previous == nil || f.previous.Len() == 0 { return defaultJoinString } return f.previous.Delimiter() } func newField(id int, rawKey string, previous delimiter) (field, error) { if len(rawKey) == 0 { return newSkipField(id), nil } key, dataType, ordinal, length, greedy := extractKeyParts(rawKey) // rawKey will have | as suffix when data type is missing if strings.HasSuffix(rawKey, dataTypeIndicator) { return nil, errMissingDatatype } if len(dataType) > 0 { if _, ok := dataTypeNames[dataType]; !ok { return nil, errInvalidDatatype } } // Conflicting prefix used. if strings.HasPrefix(key, appendIndirectPrefix) { return nil, errMixedPrefixIndirectAppend } if strings.HasPrefix(key, indirectAppendPrefix) { return nil, errMixedPrefixAppendIndirect } if strings.HasPrefix(key, skipFieldPrefix) { return newNamedSkipField(id, key[1:], length), nil } if strings.HasPrefix(key, pointerFieldPrefix) { return newPointerField(id, key[1:], length), nil } if strings.HasPrefix(key, appendFieldPrefix) { return newAppendField(id, key[1:], ordinal, length, greedy, previous), nil } if strings.HasPrefix(key, indirectFieldPrefix) { return newIndirectField(id, key[1:], dataType, length), nil } return newNormalField(id, key, dataType, ordinal, length, greedy), nil } func newSkipField(id int) skipField { return skipField{baseField{id: id}} } func newNamedSkipField(id int, key string, length int) namedSkipField { return namedSkipField{ baseField{id: id, key: key, length: length}, } } func newPointerField(id int, key string, length int) pointerField { return pointerField{ baseField{id: id, key: key, length: length}, } } func newAppendField(id int, key string, ordinal int, length int, greedy bool, previous delimiter) appendField { return appendField{ baseField: baseField{ id: id, key: key, ordinal: ordinal, length: length, greedy: greedy, }, previous: previous, } } func newIndirectField(id int, key string, dataType string, length int) indirectField { return indirectField{ baseField{ id: id, key: key, length: length, dataType: dataType, }, } } func newNormalField(id int, key string, dataType string, ordinal int, length int, greedy bool) normalField { return normalField{ baseField{ id: id, key: key, ordinal: ordinal, length: length, greedy: greedy, dataType: dataType, }, } } func extractKeyParts(rawKey string) (key string, dataType string, ordinal int, length int, greedy bool) { m := suffixRE.FindAllStringSubmatch(rawKey, -1) if m[0][3] != "" { ordinal, _ = strconv.Atoi(m[0][3]) } if m[0][5] != "" { length, _ = strconv.Atoi(m[0][5]) } if strings.EqualFold(greedySuffix, m[0][6]) { greedy = true } dataType = m[0][8] return m[0][1], dataType, ordinal, length, greedy }