log/viewer.go

194 lines
5.3 KiB
Go

package log
import (
"encoding/json"
"fmt"
"math"
"regexp"
"strings"
"time"
"apigo.cc/go/cast"
"apigo.cc/go/shell"
)
var errorLineMatcher = regexp.MustCompile(`(\w+\.go:\d+)`)
var codeFileMatcher = regexp.MustCompile(`(\w+?\.)(go|js)`)
func Viewable(line string) string {
b := ParseBaseLog(line)
if b == nil {
// 高亮错误代码
if strings.Contains(line, ".go:") {
if strings.Contains(line, "/ssgo/") || strings.Contains(line, "/ssdo/") || strings.Contains(line, "/gojs/") {
line = errorLineMatcher.ReplaceAllString(line, shell.BYellow("$1"))
} else if !strings.Contains(line, "/apigo.cc/") {
line = errorLineMatcher.ReplaceAllString(line, shell.BMagenta("$1"))
} else if !strings.Contains(line, "/go/src/") {
line = errorLineMatcher.ReplaceAllString(line, shell.BRed("$1"))
}
}
return line
}
var logTime time.Time
if strings.ContainsRune(b.LogTime, 'T') {
logTime = MakeTime(b.LogTime)
} else {
ft := cast.Float64(b.LogTime)
ts := int64(math.Floor(ft))
tns := int64((ft - float64(ts)) * 1e9)
logTime = time.Unix(ts, tns)
}
var outs []string
t1 := strings.Split(logTime.Format("01-02 15:04:05.000"), " ")
d := t1[0]
t := ""
if len(t1) > 1 {
t = t1[1]
}
t2 := strings.Split(t, ".")
s := ""
if len(t2) > 1 {
s = t2[1]
}
t = t2[0]
outs = append(outs, shell.White(shell.Bold, d+" "+t))
if s != "" {
outs = append(outs, shell.White("."+s))
}
outs = append(outs, " ", shell.Style(shell.TextWhite, shell.Dim, shell.Underline, b.TraceId))
level := ""
levelKey := ""
for _, k := range []string{"debug", "warning", "error", "info", "Debug", "Warning", "Error", "Info"} {
if b.Extra[k] != nil {
level = strings.ToLower(k)
levelKey = k
break
}
}
if b.LogType == LogTypeRequest {
method := cast.String(b.Extra["method"])
path := cast.String(b.Extra["path"])
code := cast.Int(b.Extra["responsecode"])
used := float32(cast.Float64(b.Extra["usedtime"]))
outs = append(outs, " ", shell.Cyan(shell.Bold, "REQUEST"), " ", shell.Cyan(method), " ", path)
if code >= 500 {
outs = append(outs, " ", shell.BRed(cast.String(code)))
} else if code >= 400 {
outs = append(outs, " ", shell.BYellow(cast.String(code)))
} else {
outs = append(outs, " ", shell.BGreen(cast.String(code)))
}
outs = append(outs, " ", shell.Style(shell.Dim, fmt.Sprintf("%.2fms", used)))
delete(b.Extra, "method")
delete(b.Extra, "path")
delete(b.Extra, "responsecode")
delete(b.Extra, "usedtime")
delete(b.Extra, "host")
delete(b.Extra, "scheme")
delete(b.Extra, "proto")
delete(b.Extra, "clientip")
delete(b.Extra, "serverid")
delete(b.Extra, "app")
delete(b.Extra, "node")
delete(b.Extra, "fromapp")
delete(b.Extra, "fromnode")
delete(b.Extra, "userid")
delete(b.Extra, "deviceid")
delete(b.Extra, "clientappname")
delete(b.Extra, "clientappversion")
delete(b.Extra, "sessionid")
delete(b.Extra, "requestid")
delete(b.Extra, "authlevel")
delete(b.Extra, "priority")
delete(b.Extra, "requestheaders")
delete(b.Extra, "requestdata")
delete(b.Extra, "responseheaders")
delete(b.Extra, "responsedatalength")
delete(b.Extra, "responsedata")
delete(b.Extra, "logname")
delete(b.Extra, "logtype")
delete(b.Extra, "logtime")
delete(b.Extra, "traceid")
delete(b.Extra, "imagename")
delete(b.Extra, "imagetag")
delete(b.Extra, "servername")
delete(b.Extra, "serverip")
} else if b.LogType == LogTypeStatistic {
outs = append(outs, " ", shell.Cyan(shell.Bold, "STATISTIC"))
} else if b.LogType == LogTypeTask {
outs = append(outs, " ", shell.Cyan(shell.Bold, "TASK"))
} else {
if level != "" {
msg := cast.String(b.Extra[levelKey])
delete(b.Extra, levelKey)
switch level {
case "info":
outs = append(outs, " ", shell.Cyan(msg))
case "warning":
outs = append(outs, " ", shell.Yellow(msg))
case "error":
outs = append(outs, " ", shell.Red(msg))
case "debug":
outs = append(outs, " ", msg)
}
} else if b.LogType == "undefined" {
outs = append(outs, " ", shell.Style(shell.Dim, "-"))
} else {
outs = append(outs, " ", shell.Cyan(shell.Bold, b.LogType))
}
}
callStacks := b.Extra["callStacks"]
delete(b.Extra, "callStacks")
if b.Extra != nil {
for k, v := range b.Extra {
vStr := cast.String(v)
if k == "extra" && len(vStr) > 0 && vStr[0] == '{' {
extra := make(map[string]any)
_ = json.Unmarshal([]byte(vStr), &extra)
for k2, v2 := range extra {
outs = append(outs, " ", shell.Style(shell.TextWhite, shell.Dim, shell.Italic, k2+":"), cast.String(v2))
}
} else {
outs = append(outs, " ", shell.Style(shell.TextWhite, shell.Dim, shell.Italic, k+":"), vStr)
}
}
}
if callStacks != nil {
var callStacksList []any
if csStr, ok := callStacks.(string); ok && len(csStr) > 2 && csStr[0] == '[' {
_ = json.Unmarshal([]byte(csStr), &callStacksList)
} else if csList, ok := callStacks.([]any); ok {
callStacksList = csList
}
if len(callStacksList) > 0 {
outs = append(outs, "\n")
for _, vi := range callStacksList {
v := cast.String(vi)
postfix := ""
if pos := strings.LastIndexByte(v, '/'); pos != -1 {
postfix = v[pos+1:]
v = v[:pos+1]
} else {
postfix = v
v = ""
}
outs = append(outs, " ", shell.Style(shell.Dim, v))
// 简化格式化逻辑
outs = append(outs, shell.Style(shell.TextWhite, postfix), "\n")
}
}
}
return strings.Join(outs, "")
}