github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/ginx/hlog/schema.go (about) 1 package hlog 2 3 import ( 4 "fmt" 5 "os" 6 "reflect" 7 "regexp" 8 "strings" 9 10 "github.com/bingoohuang/gg/pkg/goip" 11 "github.com/sirupsen/logrus" 12 "github.com/spyzhov/ajson" 13 ) 14 15 type colFn func(log *Log) interface{} 16 17 type col interface { 18 get(log *Log) interface{} 19 } 20 21 func (f colFn) get(log *Log) interface{} { return f(log) } 22 23 type colVFn func(log *Log, v string) interface{} 24 25 type colV interface { 26 get(log *Log, v string) interface{} 27 } 28 29 func (f colVFn) get(log *Log, v string) interface{} { return f(log, v) } 30 31 var ( 32 tagPattern = regexp.MustCompile(`httplog:"(.*?)"`) 33 34 blts = make(map[matcher]col) 35 rsps = make(map[matcher]colV) 36 reqs = make(map[matcher]colV) 37 ) 38 39 func getJSONBody(contentType, body string) string { 40 return If(strings.Contains(contentType, "json") && AnyPrefix(body, "{", "["), body, "") 41 } 42 43 func If(b bool, s1, s2 string) string { 44 if b { 45 return s1 46 } 47 48 return s2 49 } 50 51 func AnyPrefix(s string, prefixes ...string) bool { 52 for _, prefix := range prefixes { 53 if strings.HasPrefix(s, prefix) { 54 return true 55 } 56 } 57 58 return false 59 } 60 61 func jsonpath(expr, body string) string { 62 path := expr 63 if !strings.HasPrefix(expr, "$.") { 64 path = "$." + expr 65 } 66 67 nodes, err := ajson.JSONPath([]byte(body), path) 68 if err != nil { 69 logrus.Warnf("failed to eval JSONPath %s for body %s error %+v", path, body, err) 70 return "" 71 } 72 73 if len(nodes) == 1 { 74 return fmt.Sprintf("%v", nodes[0]) 75 } 76 77 return fmt.Sprintf("%v", nodes) 78 } 79 80 // nolint:lll,gochecknoinits 81 func init() { 82 blts[eq("id")] = colFn(func(l *Log) interface{} { return l.ID }) 83 blts[eq("created")] = colFn(func(l *Log) interface{} { return l.Created }) 84 blts[eq("ip")] = colFn(func(l *Log) interface{} { v, _ := goip.MainIP(); return v }) 85 blts[eq("hostname")] = colFn(func(l *Log) interface{} { v, _ := os.Hostname(); return v }) 86 blts[eq("pid")] = colFn(func(l *Log) interface{} { return os.Getpid() }) 87 blts[eq("started")] = colFn(func(l *Log) interface{} { return l.Start }) 88 blts[eq("end")] = colFn(func(l *Log) interface{} { return l.End }) 89 blts[eq("cost")] = colFn(func(l *Log) interface{} { return l.Duration.Milliseconds() }) 90 blts[eq("biz")] = colFn(func(l *Log) interface{} { return l.Biz }) 91 blts[eq("addr")] = colFn(func(l *Log) interface{} { return l.IPAddr }) 92 93 rsps[starts("head_")] = colVFn(func(l *Log, v string) interface{} { return At(l.RspHeader[v[5:]], 0) }) 94 rsps[eq("heads")] = colVFn(func(l *Log, v string) interface{} { return fmt.Sprintf("%+v", l.RspHeader) }) 95 rsps[eq("body")] = colVFn(func(l *Log, v string) interface{} { return l.RspBody }) 96 rsps[eq("json")] = colVFn(func(l *Log, v string) interface{} { return getJSONBody(At(l.RspHeader["Content-Type"], 0), l.RspBody) }) 97 rsps[starts("json_")] = colVFn(func(l *Log, v string) interface{} { return jsonpath(v[5:], l.RspBody) }) 98 rsps[eq("status")] = colVFn(func(l *Log, v string) interface{} { return l.RspStatus }) 99 100 reqs[starts("head_")] = colVFn(func(l *Log, v string) interface{} { return At(l.ReqHeader[v[5:]], 0) }) 101 reqs[eq("heads")] = colVFn(func(l *Log, v string) interface{} { return fmt.Sprintf("%+v", l.ReqHeader) }) 102 reqs[eq("body")] = colVFn(func(l *Log, v string) interface{} { return l.ReqBody }) 103 reqs[eq("json")] = colVFn(func(l *Log, v string) interface{} { return getJSONBody(At(l.ReqHeader["Content-Type"], 0), l.ReqBody) }) 104 reqs[starts("json_")] = colVFn(func(l *Log, v string) interface{} { return jsonpath(v[5:], l.ReqBody) }) 105 106 reqs[eq("method")] = colVFn(func(l *Log, v string) interface{} { return l.Method }) 107 reqs[eq("url")] = colVFn(func(l *Log, v string) interface{} { return l.URL }) 108 reqs[starts("path_")] = colVFn(func(l *Log, v string) interface{} { return l.pathVar(v[5:]) }) 109 reqs[eq("paths")] = colVFn(func(l *Log, v string) interface{} { return l.pathVars() }) 110 reqs[starts("query_")] = colVFn(func(l *Log, v string) interface{} { return l.queryVar(v[6:]) }) 111 reqs[eq("queries")] = colVFn(func(l *Log, v string) interface{} { return l.queryVars() }) 112 reqs[starts("param_")] = colVFn(func(l *Log, v string) interface{} { return l.paramVar(v[6:]) }) 113 reqs[eq("params")] = colVFn(func(l *Log, v string) interface{} { return l.paramVars() }) 114 } 115 116 func (s *TableCol) parseComment() { 117 tag := strings.ToLower(s.Name) 118 if tag != "" { 119 sub := tagPattern.FindAllStringSubmatch(s.Comment, 1) 120 if len(sub) > 0 { 121 tag = sub[0][1] 122 } 123 } 124 125 switch { 126 case strings.HasPrefix(tag, "req_"): 127 s.ValueGetter = createValueGetter(tag[4:], reqs) 128 case strings.HasPrefix(tag, "rsp_"): 129 s.ValueGetter = createValueGetter(tag[4:], rsps) 130 case strings.HasPrefix(tag, "ctx_"): 131 s.ValueGetter = createCtxValueGetter(tag[4:]) 132 case tag == "-": 133 s.ValueGetter = nil 134 default: 135 s.ValueGetter = createBuiltinValueGetter(tag) 136 } 137 138 if s.ValueGetter != nil { 139 s.ValueGetter = s.wrapMaxLength(s.ValueGetter) 140 } 141 } 142 143 func (s *TableCol) wrapMaxLength(col col) col { 144 // Caution: use temporary to store for later usage in function 145 // avoid directly use s.MaxLength in the following that will cause problems. 146 maxLength := s.MaxLength 147 148 return colFn(func(l *Log) interface{} { 149 v := col.get(l) 150 151 if v == nil { 152 if s.IsNullable() { 153 return v 154 } else { 155 return "" 156 } 157 } 158 159 if maxLength <= 0 { 160 return v 161 } 162 163 switch reflect.TypeOf(v).String() { 164 case "int", "int8", "int16", "int32", "int64", 165 "uint", "uint8", "uint16", "uint32", "uint64", 166 "bool", 167 "float32", "float64", 168 "time.Time": 169 return v 170 } 171 172 return Abbreviate(fmt.Sprintf("%v", v), maxLength) 173 }) 174 } 175 176 func findGetterV(tag string, m map[matcher]colV) colV { 177 for k, v := range m { 178 if k.matches(tag) { 179 return v 180 } 181 } 182 183 return nil 184 } 185 186 func findGetter(tag string, m map[matcher]col) col { 187 for k, v := range m { 188 if k.matches(tag) { 189 return v 190 } 191 } 192 193 return nil 194 } 195 196 type v struct { 197 colV 198 v string 199 } 200 201 func (v v) get(log *Log) interface{} { 202 return v.colV.get(log, v.v) 203 } 204 205 func createBuiltinValueGetter(tag string) col { 206 return findGetter(tag, blts) 207 } 208 209 func createCtxValueGetter(tag string) col { 210 return colFn(func(l *Log) interface{} { 211 return l.Attrs[tag] 212 }) 213 } 214 215 func createValueGetter(tag string, m map[matcher]colV) col { 216 getterV := findGetterV(tag, m) 217 if getterV == nil { 218 return nil 219 } 220 221 return &v{colV: getterV, v: tag} 222 } 223 224 type matcher interface { 225 matches(tag string) bool 226 } 227 228 type equalMatcher struct { 229 value string 230 } 231 232 func eq(v string) matcher { 233 return equalMatcher{value: v} 234 } 235 236 func (r equalMatcher) matches(tag string) bool { 237 return r.value == tag 238 } 239 240 type startsMatcher struct { 241 Value string 242 } 243 244 func (r startsMatcher) matches(tag string) bool { 245 return strings.HasPrefix(tag, r.Value) 246 } 247 248 func starts(v string) matcher { 249 return startsMatcher{Value: v} 250 }