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  }