github.com/observiq/carbon@v0.9.11-0.20200820160507-1b872e368a5e/operator/helper/severity_builder.go (about)

     1  package helper
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/observiq/carbon/entry"
     9  	"github.com/observiq/carbon/operator"
    10  )
    11  
    12  const minSeverity = 0
    13  const maxSeverity = 100
    14  
    15  // map[string or int input]sev-level
    16  func getBuiltinMapping(name string) severityMap {
    17  	switch name {
    18  	case "none":
    19  		return map[string]entry.Severity{}
    20  	case "aliases":
    21  		return map[string]entry.Severity{
    22  			"default":     entry.Default,
    23  			"trace":       entry.Trace,
    24  			"debug":       entry.Debug,
    25  			"info":        entry.Info,
    26  			"notice":      entry.Notice,
    27  			"warning":     entry.Warning,
    28  			"error":       entry.Error,
    29  			"critical":    entry.Critical,
    30  			"alert":       entry.Alert,
    31  			"emergency":   entry.Emergency,
    32  			"catastrophe": entry.Catastrophe,
    33  		}
    34  	default:
    35  		mapping := getBuiltinMapping("aliases")
    36  		mapping.add(entry.Warning, "warn")
    37  		mapping.add(entry.Error, "err")
    38  		mapping.add(entry.Critical, "crit")
    39  		return mapping
    40  	}
    41  }
    42  
    43  func (s severityMap) add(severity entry.Severity, parseableValues ...string) {
    44  	for _, str := range parseableValues {
    45  		s[str] = severity
    46  	}
    47  }
    48  
    49  const (
    50  	// HTTP2xx is a special key that is represents a range from 200 to 299. Literal value is "2xx"
    51  	HTTP2xx = "2xx"
    52  
    53  	// HTTP3xx is a special key that is represents a range from 300 to 399. Literal value is "3xx"
    54  	HTTP3xx = "3xx"
    55  
    56  	// HTTP4xx is a special key that is represents a range from 400 to 499. Literal value is "4xx"
    57  	HTTP4xx = "4xx"
    58  
    59  	// HTTP5xx is a special key that is represents a range from 500 to 599. Literal value is "5xx"
    60  	HTTP5xx = "5xx"
    61  )
    62  
    63  func NewSeverityParserConfig() SeverityParserConfig {
    64  	return SeverityParserConfig{}
    65  }
    66  
    67  // SeverityParserConfig allows users to specify how to parse a severity from a field.
    68  type SeverityParserConfig struct {
    69  	ParseFrom *entry.Field                `json:"parse_from,omitempty" yaml:"parse_from,omitempty"`
    70  	Preserve  bool                        `json:"preserve,omitempty"   yaml:"preserve,omitempty"`
    71  	Preset    string                      `json:"preset,omitempty"     yaml:"preset,omitempty"`
    72  	Mapping   map[interface{}]interface{} `json:"mapping,omitempty"    yaml:"mapping,omitempty"`
    73  }
    74  
    75  // Build builds a SeverityParser from a SeverityParserConfig
    76  func (c *SeverityParserConfig) Build(context operator.BuildContext) (SeverityParser, error) {
    77  	operatorMapping := getBuiltinMapping(c.Preset)
    78  
    79  	for severity, unknown := range c.Mapping {
    80  		sev, err := validateSeverity(severity)
    81  		if err != nil {
    82  			return SeverityParser{}, err
    83  		}
    84  
    85  		switch u := unknown.(type) {
    86  		case []interface{}: // check before interface{}
    87  			for _, value := range u {
    88  				v, err := parseableValues(value)
    89  				if err != nil {
    90  					return SeverityParser{}, err
    91  				}
    92  				operatorMapping.add(sev, v...)
    93  			}
    94  		case interface{}:
    95  			v, err := parseableValues(u)
    96  			if err != nil {
    97  				return SeverityParser{}, err
    98  			}
    99  			operatorMapping.add(sev, v...)
   100  		}
   101  	}
   102  
   103  	if c.ParseFrom == nil {
   104  		return SeverityParser{}, fmt.Errorf("missing required field 'parse_from'")
   105  	}
   106  
   107  	p := SeverityParser{
   108  		ParseFrom: *c.ParseFrom,
   109  		Preserve:  c.Preserve,
   110  		Mapping:   operatorMapping,
   111  	}
   112  
   113  	return p, nil
   114  }
   115  
   116  func validateSeverity(severity interface{}) (entry.Severity, error) {
   117  	if sev, err := getBuiltinMapping("aliases").find(severity); err != nil {
   118  		return entry.Nil, err
   119  	} else if sev != entry.Nil {
   120  		return sev, nil
   121  	}
   122  
   123  	// If integer between 0 and 100
   124  	var intSev int
   125  	switch s := severity.(type) {
   126  	case int:
   127  		intSev = s
   128  	case string:
   129  		i, err := strconv.ParseInt(s, 10, 8)
   130  		if err != nil {
   131  			return entry.Nil, fmt.Errorf("%s cannot be used as a severity", severity)
   132  		}
   133  		intSev = int(i)
   134  	default:
   135  		return entry.Nil, fmt.Errorf("type %T cannot be used as a severity (%v)", severity, severity)
   136  	}
   137  
   138  	if intSev < minSeverity || intSev > maxSeverity {
   139  		return entry.Nil, fmt.Errorf("severity must be between %d and %d", minSeverity, maxSeverity)
   140  	}
   141  	return entry.Severity(intSev), nil
   142  }
   143  
   144  func isRange(value interface{}) (int, int, bool) {
   145  	rawMap, ok := value.(map[interface{}]interface{})
   146  	if !ok {
   147  		return 0, 0, false
   148  	}
   149  
   150  	min, minOK := rawMap["min"]
   151  	max, maxOK := rawMap["max"]
   152  	if !minOK || !maxOK {
   153  		return 0, 0, false
   154  	}
   155  
   156  	minInt, minOK := min.(int)
   157  	maxInt, maxOK := max.(int)
   158  	if !minOK || !maxOK {
   159  		return 0, 0, false
   160  	}
   161  
   162  	return minInt, maxInt, true
   163  }
   164  
   165  func expandRange(min, max int) []string {
   166  	if min > max {
   167  		min, max = max, min
   168  	}
   169  
   170  	rangeOfStrings := []string{}
   171  	for i := min; i <= max; i++ {
   172  		rangeOfStrings = append(rangeOfStrings, strconv.Itoa(i))
   173  	}
   174  	return rangeOfStrings
   175  }
   176  
   177  func parseableValues(value interface{}) ([]string, error) {
   178  	switch v := value.(type) {
   179  	case int:
   180  		return []string{strconv.Itoa(v)}, nil // store as string because we will compare as string
   181  	case string:
   182  		switch v {
   183  		case HTTP2xx:
   184  			return expandRange(200, 299), nil
   185  		case HTTP3xx:
   186  			return expandRange(300, 399), nil
   187  		case HTTP4xx:
   188  			return expandRange(400, 499), nil
   189  		case HTTP5xx:
   190  			return expandRange(500, 599), nil
   191  		default:
   192  			return []string{strings.ToLower(v)}, nil
   193  		}
   194  	case []byte:
   195  		return []string{strings.ToLower(string(v))}, nil
   196  	default:
   197  		min, max, ok := isRange(v)
   198  		if ok {
   199  			return expandRange(min, max), nil
   200  		}
   201  		return nil, fmt.Errorf("type %T cannot be parsed as a severity", v)
   202  	}
   203  }