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 }