github.com/magnusbaeck/logstash-filter-verifier/v2@v2.0.0-pre.1/logstash/fieldset.go (about)

     1  // Copyright (c) 2015-2018 Magnus Bäck <magnus@noun.se>
     2  
     3  package logstash
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"sort"
     9  	"strings"
    10  )
    11  
    12  // FieldSet contains a set of fields for a Logstash event and can be
    13  // marshaled as a Logstash-compatible string that's acceptable to an
    14  // add_field option for an input or filter.
    15  type FieldSet map[string]interface{}
    16  
    17  // IsValid inspects the field set and returns an error if there are
    18  // any values that Logstash obviously would disapprove of (like
    19  // objects in arrays).
    20  func (fs FieldSet) IsValid() error {
    21  	if fs == nil {
    22  		return errors.New("Fields must not be \"null\".")
    23  	}
    24  	_, err := fs.LogstashHash()
    25  	return err
    26  }
    27  
    28  // LogstashHash converts a FieldSet into a Logstash-style hash that
    29  // e.g. is accepted by an add_field directive in a configuration file,
    30  // i.e. it has the form { "key1" => "value1" ... "keyN" => "valueN" }.
    31  func (fs FieldSet) LogstashHash() (string, error) {
    32  	result := make([]string, 0, len(fs))
    33  	for k, v := range fs {
    34  		ks, s, err := serializeAsLogstashLiteral(k, v)
    35  		if err != nil || len(ks) != len(s) {
    36  			return "", fmt.Errorf("Problem converting field %q to Logstash format: %s", k, err)
    37  		}
    38  		for i, k := range ks {
    39  			if strings.LastIndex(k, "[") == 0 {
    40  				k = strings.Trim(k, "[]")
    41  			}
    42  			result = append(result, fmt.Sprintf("%q => %s", k, s[i]))
    43  		}
    44  	}
    45  	// Sort the strings to make writing tests easier when there's
    46  	// more than one field in the map.
    47  	sort.Strings(result)
    48  	return "{ " + strings.Join(result, " ") + " }", nil
    49  }
    50  
    51  // serializeAsLogstashLiteral serializes a single entity into a
    52  // Logstash value literal.
    53  func serializeAsLogstashLiteral(k string, v interface{}) ([]string, []string, error) {
    54  	k = fmt.Sprintf("[%s]", k)
    55  	switch v := v.(type) {
    56  	case bool:
    57  		return []string{k}, []string{fmt.Sprintf("%v", v)}, nil
    58  	case float64:
    59  		// large floats must not be converted to exponential notation, because this is not valid for Logstash
    60  		// https://github.com/elastic/logstash/blob/master/logstash-core/lib/logstash/config/grammar.treetop#L92
    61  		if !strings.Contains(fmt.Sprintf("%v", v), "e") {
    62  			return []string{k}, []string{fmt.Sprintf("%v", v)}, nil
    63  		}
    64  		return []string{k}, []string{fmt.Sprintf("%f", v)}, nil
    65  	case string:
    66  		return []string{k}, []string{fmt.Sprintf("%q", v)}, nil
    67  	case []interface{}:
    68  		result := make([]string, len(v))
    69  		for i, element := range v {
    70  			if v, ok := element.(map[string]interface{}); ok {
    71  				return []string{k}, []string{}, fmt.Errorf("Unsupported type %T in %T: %#v", v, []interface{}{}, v)
    72  			}
    73  			ks, s, err := serializeAsLogstashLiteral(k, element)
    74  			if err != nil {
    75  				return ks, []string{}, err
    76  			}
    77  			result[i] = s[0]
    78  		}
    79  		return []string{k}, []string{"[" + strings.Join(result, ", ") + "]"}, nil
    80  	case map[string]interface{}:
    81  		result := make([]string, 0, len(v))
    82  		keys := make([]string, 0, len(v))
    83  		i := 0
    84  		for ik, iv := range v {
    85  			ks, s, err := serializeAsLogstashLiteral(ik, iv)
    86  			if err != nil {
    87  				return nil, nil, err
    88  			}
    89  			for i := range ks {
    90  				ks[i] = fmt.Sprintf("%s%s", k, ks[i])
    91  			}
    92  			result = append(result, s...)
    93  			keys = append(keys, ks...)
    94  			i++
    95  		}
    96  		return keys, result, nil
    97  	default:
    98  		return []string{k}, []string{}, fmt.Errorf("Unsupported type %T: %#v", v, v)
    99  	}
   100  }