nightingale/http/router_collect_rule.go

341 lines
8.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package http
import (
"fmt"
"regexp"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/logger"
"github.com/didi/nightingale/v5/cache"
"github.com/didi/nightingale/v5/models"
)
type collectRuleForm struct {
ClasspathId int64 `json:"classpath_id"`
PrefixMatch int `json:"prefix_match"`
Name string `json:"name"`
Note string `json:"note"`
Step int `json:"step"`
Type string `json:"type"`
Data string `json:"data"`
AppendTags string `json:"append_tags"`
}
func collectRuleAdd(c *gin.Context) {
var f collectRuleForm
bind(c, &f)
me := loginUser(c).MustPerm("collect_rule_create")
cr := models.CollectRule{
ClasspathId: f.ClasspathId,
PrefixMatch: f.PrefixMatch,
Name: f.Name,
Note: f.Note,
Step: f.Step,
Type: f.Type,
Data: f.Data,
AppendTags: f.AppendTags,
CreateBy: me.Username,
UpdateBy: me.Username,
}
renderMessage(c, cr.Add())
}
func collectRulePut(c *gin.Context) {
var f collectRuleForm
bind(c, &f)
me := loginUser(c).MustPerm("collect_rule_modify")
cr := CollectRule(urlParamInt64(c, "id"))
cr.PrefixMatch = f.PrefixMatch
cr.Name = f.Name
cr.Note = f.Note
cr.Step = f.Step
cr.Type = f.Type
cr.Data = f.Data
cr.AppendTags = f.AppendTags
cr.UpdateAt = time.Now().Unix()
cr.UpdateBy = me.Username
renderMessage(c, cr.Update(
"prefix_match",
"name",
"note",
"step",
"type",
"data",
"update_at",
"update_by",
"append_tags",
))
}
func collectRuleDel(c *gin.Context) {
var f idsForm
bind(c, &f)
f.Validate()
loginUser(c).MustPerm("collect_rule_delete")
renderMessage(c, models.CollectRulesDel(f.Ids))
}
func collectRuleGets(c *gin.Context) {
classpathId := urlParamInt64(c, "id")
where := "classpath_id = ?"
param := []interface{}{classpathId}
typ := queryStr(c, "type", "")
if typ != "" {
where += " and type = ?"
param = append(param, typ)
}
objs, err := models.CollectRuleGets(where, param...)
renderData(c, objs, err)
}
func collectRuleGetsByIdent(c *gin.Context) {
ident := queryStr(c, "ident")
objs := cache.CollectRulesOfIdent.GetBy(ident)
renderData(c, objs, nil)
}
type Summary struct {
LatestUpdatedAt int64 `json:"latestUpdatedAt"`
Total int `json:"total"`
}
func collectRuleSummaryGetByIdent(c *gin.Context) {
ident := queryStr(c, "ident")
var summary Summary
objs := cache.CollectRulesOfIdent.GetBy(ident)
total := len(objs)
if total > 0 {
summary.Total = total
var latestUpdatedAt int64
for _, obj := range objs {
if latestUpdatedAt < obj.UpdateAt {
latestUpdatedAt = obj.UpdateAt
}
}
summary.LatestUpdatedAt = latestUpdatedAt
}
renderData(c, summary, nil)
}
type RegExpCheck struct {
Success bool `json:"success"`
Data []map[string]string `json:"tags"`
}
var RegExpExcludePatition string = "```EXCLUDE```"
func regExpCheck(c *gin.Context) {
param := make(map[string]string)
dangerous(c.ShouldBind(&param))
ret := &RegExpCheck{
Success: true,
Data: make([]map[string]string, 0),
}
// 处理时间格式
if t, ok := param["time"]; !ok || t == "" {
tmp := map[string]string{"time": "time参数不存在或为空"}
ret.Data = append(ret.Data, tmp)
} else {
timePat, _ := GetPatAndTimeFormat(param["time"])
if timePat == "" {
tmp := map[string]string{"time": genErrMsg("时间格式")}
ret.Data = append(ret.Data, tmp)
} else {
suc, tRes, _ := checkRegPat(timePat, param["log"], true)
if !suc {
ret.Success = false
tRes = genErrMsg("时间格式")
}
tmp := map[string]string{"time": tRes}
ret.Data = append(ret.Data, tmp)
}
}
// 计算方式
calc_method := param["calc_method"]
// 处理主正则(with exclude)
if re, ok := param["re"]; !ok || re == "" {
tmp := map[string]string{"re": "re参数不存在或为空"}
ret.Data = append(ret.Data, tmp)
} else {
// 处理exclude的情况
exclude := ""
if strings.Contains(re, RegExpExcludePatition) {
l := strings.Split(re, RegExpExcludePatition)
if len(l) >= 2 {
param["re"] = l[0]
exclude = l[1]
}
}
// 匹配主正则
suc, reRes, isSub := checkRegPat(param["re"], param["log"], false)
if !suc {
ret.Success = false
reRes = genErrMsg("主正则")
}
if calc_method != "" && calc_method != "cnt" && !isSub {
ret.Success = false
reRes = genSubErrMsg("主正则")
}
tmp := map[string]string{"主正则": reRes}
ret.Data = append(ret.Data, tmp)
// 匹配exclude, 这个不影响失败
if exclude != "" {
suc, exRes, _ := checkRegPat(exclude, param["log"], false)
if !suc {
//ret.Success = false
exRes = "未匹配到排除串,请检查是否符合预期"
}
tmp := map[string]string{"排除串": exRes}
ret.Data = append(ret.Data, tmp)
}
}
// 处理tags
var nonTagKey = map[string]bool{
"re": true,
"log": true,
"time": true,
"calc_method": true,
}
for tagk, pat := range param {
// 如果不是tag就继续循环
if _, ok := nonTagKey[tagk]; ok {
continue
}
suc, tagRes, isSub := checkRegPat(pat, param["log"], false)
if !suc {
// 正则错误
ret.Success = false
tagRes = genErrMsg(tagk)
} else if !isSub {
// 未匹配出子串
ret.Success = false
tagRes = genSubErrMsg(tagk)
} else if includeIllegalChar(tagRes) || includeIllegalChar(tagk) {
// 保留字报错
ret.Success = false
tagRes = genIllegalCharErrMsg()
}
tmp := map[string]string{tagk: tagRes}
ret.Data = append(ret.Data, tmp)
}
renderData(c, ret, nil)
}
//根据配置的时间格式获取对应的正则匹配pattern和time包用的时间格式
func GetPatAndTimeFormat(tf string) (string, string) {
var pat, timeFormat string
switch tf {
case "dd/mmm/yyyy:HH:MM:SS":
pat = `([012][0-9]|3[01])/[JFMASOND][a-z]{2}/(2[0-9]{3}):([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "02/Jan/2006:15:04:05"
case "dd/mmm/yyyy HH:MM:SS":
pat = `([012][0-9]|3[01])/[JFMASOND][a-z]{2}/(2[0-9]{3})\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "02/Jan/2006 15:04:05"
case "yyyy-mm-ddTHH:MM:SS":
pat = `(2[0-9]{3})-(0[1-9]|1[012])-([012][0-9]|3[01])T([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "2006-01-02T15:04:05"
case "dd-mmm-yyyy HH:MM:SS":
pat = `([012][0-9]|3[01])-[JFMASOND][a-z]{2}-(2[0-9]{3})\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "02-Jan-2006 15:04:05"
case "yyyy-mm-dd HH:MM:SS":
pat = `(2[0-9]{3})-(0[1-9]|1[012])-([012][0-9]|3[01])\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "2006-01-02 15:04:05"
case "yyyy/mm/dd HH:MM:SS":
pat = `(2[0-9]{3})/(0[1-9]|1[012])/([012][0-9]|3[01])\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "2006/01/02 15:04:05"
case "yyyymmdd HH:MM:SS":
pat = `(2[0-9]{3})(0[1-9]|1[012])([012][0-9]|3[01])\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "20060102 15:04:05"
case "mmm dd HH:MM:SS":
pat = `[JFMASOND][a-z]{2}\s+([1-9]|[1-2][0-9]|3[01])\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "Jan 2 15:04:05"
case "mmdd HH:MM:SS":
pat = `(0[1-9]|1[012])([012][0-9]|3[01])\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "0102 15:04:05"
case "dd mmm yyyy HH:MM:SS":
pat = `([012][0-9]|3[01])\s+[JFMASOND][a-z]{2}\s+(2[0-9]{3})\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "02 Jan 2006 15:04:05"
default:
logger.Errorf("match time pac failed : [timeFormat:%s]", tf)
return "", ""
}
return pat, timeFormat
}
// 出错信息直接放在body里
func checkRegPat(pat string, log string, origin bool) (succ bool, result string, isSub bool) {
if pat == "" {
return false, "", false
}
reg, err := regexp.Compile(pat)
if err != nil {
return false, "", false
}
res := reg.FindStringSubmatch(log)
switch len(res) {
// 没查到
case 0:
return false, "", false
// 没查到括号内的串,返回整个匹配串
case 1:
return true, res[0], false
// 查到了,默认取第一个串
default:
var msg string
if origin {
msg = res[0]
isSub = false
} else {
msg = res[1]
isSub = true
}
return true, msg, isSub
}
}
func includeIllegalChar(s string) bool {
illegalChars := ":,=\r\n\t"
return strings.ContainsAny(s, illegalChars)
}
// 生成返回错误信息
func genErrMsg(sign string) string {
return fmt.Sprintf("正则匹配失败,请认真检查您[%s]的配置", sign)
}
// 生成子串匹配错误信息
func genSubErrMsg(sign string) string {
return fmt.Sprintf("正则匹配成功。但根据配置,并没有获取到()内的子串,请认真检查您[%s]的配置", sign)
}
// 生成子串匹配错误信息
func genIllegalCharErrMsg() string {
return `正则匹配成功。但是tag的key或者value包含非法字符:[:,/=\r\n\t], 请重新调整`
}