github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/logline/parse.go (about)

     1  package logline
     2  
     3  import (
     4  	"errors"
     5  	"net/url"
     6  	"regexp"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  	"unsafe"
    11  
    12  	"github.com/bingoohuang/gg/pkg/ss"
    13  )
    14  
    15  var filters = map[string]Converter{
    16  	"path":     UriPath(),
    17  	"duration": DurationPath(),
    18  }
    19  
    20  type Pattern struct {
    21  	Pattern    string
    22  	Converters map[string]Converter
    23  	Dots       []Dot
    24  }
    25  
    26  // SliceToString preferred for large body payload (zero allocation and faster)
    27  func SliceToString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) }
    28  
    29  func (p Pattern) Names() (names []string) {
    30  	for _, dot := range p.Dots {
    31  		if dot.Valid() {
    32  			names = append(names, dot.Name)
    33  		}
    34  	}
    35  	return
    36  }
    37  
    38  func (p Pattern) ParseBytes(s []byte) (map[string]interface{}, bool) {
    39  	return p.Parse(SliceToString(s))
    40  }
    41  
    42  func (p Pattern) Parse(s string) (m map[string]interface{}, ok bool) {
    43  	m = make(map[string]interface{})
    44  	count := 0
    45  	for _, dot := range p.Dots {
    46  		if dot.EOF {
    47  			if dot.Valid() {
    48  				val, _ := dot.Converters.Convert(s)
    49  				m[dot.Name] = val
    50  			}
    51  			count++
    52  			break
    53  		}
    54  		pos := strings.Index(s, dot.Anchor)
    55  		if pos < 0 {
    56  			break
    57  		}
    58  
    59  		count++
    60  		if dot.Valid() {
    61  			val, _ := dot.Converters.Convert(s[:pos])
    62  			m[dot.Name] = val
    63  		}
    64  
    65  		s = s[pos+len(dot.Anchor):]
    66  	}
    67  
    68  	ok = count == len(p.Dots)
    69  
    70  	return
    71  }
    72  
    73  type Dot struct {
    74  	Type
    75  	Anchor     string
    76  	Name       string
    77  	Sample     string
    78  	Converters Converters
    79  	EOF        bool
    80  }
    81  
    82  func (d Dot) Valid() bool {
    83  	return !(d.Name == "-" || d.Name == "")
    84  }
    85  
    86  var ErrBadPattern = errors.New("bad pattern")
    87  
    88  var digitsRegexp = regexp.MustCompile(`^\d+$`)
    89  
    90  type Type int
    91  
    92  const (
    93  	String Type = iota
    94  	DateTime
    95  	Float
    96  	Digits
    97  )
    98  
    99  func WithReplace(pairs ...string) func(*Option) {
   100  	return func(option *Option) {
   101  		option.Replaces = pairs
   102  	}
   103  }
   104  
   105  type Option struct {
   106  	Replaces []string
   107  }
   108  
   109  func (o Option) Replace(s string) string {
   110  	for i := 0; i+1 < len(o.Replaces); i += 2 {
   111  		s = strings.ReplaceAll(s, o.Replaces[i], o.Replaces[i+1])
   112  	}
   113  
   114  	return s
   115  }
   116  
   117  type (
   118  	OptionFn  func(*Option)
   119  	OptionFns []OptionFn
   120  )
   121  
   122  func (fs OptionFns) Apply(option *Option) {
   123  	for _, f := range fs {
   124  		f(option)
   125  	}
   126  }
   127  
   128  func NewPattern(sample, pattern string, options ...OptionFn) (*Pattern, error) {
   129  	var dots []Dot
   130  
   131  	option := &Option{}
   132  	OptionFns(options).Apply(option)
   133  	sample = option.Replace(sample)
   134  	for {
   135  		pos := strings.IndexByte(pattern, '#')
   136  		if pos < 0 && pattern == "" {
   137  			break
   138  		}
   139  
   140  		left, leftSample := "", ""
   141  		var anchor string
   142  
   143  		if pos < 0 {
   144  			left = pattern
   145  			leftSample = sample
   146  		} else {
   147  			more := nextCrosses(pos, pattern)
   148  
   149  			if pos+more >= len(sample) {
   150  				return nil, ErrBadPattern
   151  			}
   152  
   153  			anchor = sample[pos : pos+more+1]
   154  			left = pattern[:pos]
   155  			leftSample = sample[:pos]
   156  			pos += more
   157  		}
   158  		parts := split(strings.TrimSpace(left), "|")
   159  		name := parts[0]
   160  
   161  		var converters Converters
   162  		typ := String
   163  
   164  		dotSample := strings.Trim(leftSample, " ")
   165  		if ss.Contains(name, "time", "date") {
   166  			converters = append(converters, TimeValue(dotSample))
   167  			typ = DateTime
   168  		} else if digitsRegexp.MatchString(dotSample) {
   169  			converters = append(converters, DigitsValue())
   170  			typ = Digits
   171  		} else if strings.Count(dotSample, ".") == 1 &&
   172  			digitsRegexp.MatchString(strings.ReplaceAll(dotSample, ".", "")) {
   173  			converters = append(converters, FloatValue())
   174  			typ = Float
   175  		}
   176  
   177  		for i := 1; i < len(parts); i++ {
   178  			converters = append(converters, filters[parts[i]])
   179  		}
   180  
   181  		if typ == String && len(converters) > 0 {
   182  			if v, err := converters.Convert(dotSample); err == nil {
   183  				switch v.(type) {
   184  				case float64, float32:
   185  					typ = Float
   186  				case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
   187  					typ = Digits
   188  				}
   189  			}
   190  		}
   191  
   192  		dot := Dot{Anchor: anchor, Name: name, Converters: converters, Type: typ, Sample: dotSample, EOF: pos < 0}
   193  		dots = append(dots, dot)
   194  
   195  		if pos < 0 {
   196  			break
   197  		}
   198  		pattern = pattern[pos+1:]
   199  		sample = sample[pos+1:]
   200  	}
   201  
   202  	return &Pattern{Pattern: pattern, Dots: dots}, nil
   203  }
   204  
   205  func nextCrosses(pos int, pattern string) int {
   206  	for i := pos + 1; i < len(pattern); i++ {
   207  		if pattern[i] != '#' {
   208  			return i - pos - 1
   209  		}
   210  	}
   211  	return 0
   212  }
   213  
   214  func split(name, sep string) []string {
   215  	var parts []string
   216  	for _, v := range strings.Split(name, sep) {
   217  		if v = strings.TrimSpace(v); v != "" {
   218  			parts = append(parts, v)
   219  		}
   220  	}
   221  
   222  	if len(parts) == 0 {
   223  		return []string{name}
   224  	}
   225  
   226  	return parts
   227  }
   228  
   229  type Converter interface {
   230  	Convert(v interface{}) (interface{}, error)
   231  }
   232  
   233  type Converters []Converter
   234  
   235  func (c Converters) Convert(v interface{}) (interface{}, error) {
   236  	for _, f := range c {
   237  		if vv, err := f.Convert(v); err != nil {
   238  			return v, err
   239  		} else {
   240  			v = vv
   241  		}
   242  	}
   243  
   244  	return v, nil
   245  }
   246  
   247  func FloatValue() Converter             { return &floatValue{} }
   248  func DigitsValue() Converter            { return &digitsValue{} }
   249  func TimeValue(layout string) Converter { return &timeValue{layout: layout} }
   250  func UriPath() Converter                { return &uriPath{} }
   251  func DurationPath() Converter           { return &durationPath{} }
   252  
   253  type (
   254  	durationPath struct{}
   255  	uriPath      struct{}
   256  	timeValue    struct{ layout string }
   257  	digitsValue  struct{}
   258  	floatValue   struct{}
   259  )
   260  
   261  func (t timeValue) Convert(v interface{}) (interface{}, error) {
   262  	return time.Parse(t.layout, v.(string))
   263  }
   264  
   265  func (t digitsValue) Convert(v interface{}) (interface{}, error) {
   266  	vs := v.(string)
   267  	if vs == "" || vs == "-" {
   268  		return 0, nil
   269  	}
   270  	return strconv.Atoi(vs)
   271  }
   272  
   273  func (t floatValue) Convert(v interface{}) (interface{}, error) {
   274  	vs := v.(string)
   275  	if vs == "" || vs == "-" {
   276  		return float64(0), nil
   277  	}
   278  	return strconv.ParseFloat(vs, 64)
   279  }
   280  
   281  func (uriPath) Convert(v interface{}) (interface{}, error) {
   282  	u, err := url.Parse(v.(string))
   283  	if err != nil {
   284  		return nil, err
   285  	}
   286  
   287  	return u.Path, nil
   288  }
   289  
   290  func (d durationPath) Convert(v interface{}) (interface{}, error) {
   291  	du, err := time.ParseDuration(v.(string))
   292  	if err != nil {
   293  		return v, err
   294  	}
   295  
   296  	return du.Seconds(), nil
   297  }