log/viewer.go

178 lines
3.8 KiB
Go
Raw Permalink Normal View History

package log
import (
"encoding/json"
"fmt"
"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 {
line = strings.TrimSpace(line)
if !strings.HasPrefix(line, "[") {
// Fallback highlight for non-array strings
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 arr []any
if err := json.Unmarshal([]byte(line), &arr); err != nil {
return line
}
if len(arr) < 3 {
return line // At least Name, Type, Time
}
logType := cast.String(arr[1])
if logType == "" {
logType = "undefined"
}
meta := GetMeta(logType)
if len(meta) == 0 {
// Fallback rendering
return fallbackRenderArray(arr)
}
var builder strings.Builder
for i, v := range arr {
if v == nil {
continue
}
if i >= len(meta) {
// Unmapped trailing values, just print them
builder.WriteString(" ")
builder.WriteString(shell.Style(shell.Dim, fmt.Sprintf("Index%d:", i)))
builder.WriteString(cast.String(v))
continue
}
m := meta[i]
if m.Hide {
continue
}
if m.Name == "Extra" {
extraMap, ok := v.(map[string]any)
if ok && len(extraMap) > 0 {
for k, ev := range extraMap {
builder.WriteString(" ")
builder.WriteString(shell.Style(shell.TextWhite, shell.Dim, shell.Italic, k+":"))
vStr := ""
switch ev.(type) {
case map[string]any, []any:
vStr, _ = cast.ToJSON(ev)
default:
vStr = cast.String(ev)
}
builder.WriteString(vStr)
}
}
continue
}
if m.Name == "CallStacks" {
callStacksList, ok := v.([]any)
if ok && len(callStacksList) > 0 {
builder.WriteString("\n")
for _, vi := range callStacksList {
vStr := cast.String(vi)
postfix := ""
if pos := strings.LastIndexByte(vStr, '/'); pos != -1 {
postfix = vStr[pos+1:]
vStr = vStr[:pos+1]
} else {
postfix = vStr
vStr = ""
}
builder.WriteString(" ")
builder.WriteString(shell.Style(shell.Dim, vStr))
builder.WriteString(shell.Style(shell.TextWhite, postfix))
builder.WriteString("\n")
}
}
continue
}
// Handle normal fields
vStr := ""
if m.Format == "time" {
// Convert int64 ns to time string
logTime := time.Unix(0, cast.Int64(v))
vStr = logTime.Format("01-02 15:04:05.000")
if m.Color == "" {
builder.WriteString(shell.White(shell.Bold, vStr))
builder.WriteString(" ")
continue
}
} else {
vStr = cast.String(v)
if vStr == "" {
continue
}
}
if builder.Len() > 0 {
builder.WriteString(" ")
}
if !m.WithoutKey {
builder.WriteString(shell.Style(shell.TextWhite, shell.Dim, shell.Italic, m.Name+":"))
}
builder.WriteString(applyColor(vStr, m.Color))
}
return builder.String()
}
func applyColor(text string, color string) string {
switch color {
case "red":
return shell.Red(text)
case "cyan":
return shell.Cyan(text)
case "blue":
return shell.Blue(text)
case "magenta":
return shell.Magenta(text)
case "yellow":
return shell.Yellow(text)
case "green":
return shell.Green(text)
case "gray", "darkGray":
return shell.Style(shell.Dim, text)
default:
return text
}
}
func fallbackRenderArray(arr []any) string {
var builder strings.Builder
for i, v := range arr {
if i > 0 {
builder.WriteString(" ")
}
builder.WriteString(cast.String(v))
}
return builder.String()
}