log/logger.go

301 lines
6.7 KiB
Go
Raw Permalink 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 log
import (
"fmt"
"log"
"os"
"regexp"
"strings"
"time"
"apigo.cc/go/cast"
)
type Logger struct {
config Config
level LevelType
goLogger *log.Logger
file *FileWriter
writer Writer
formatter Formatter
truncations []string
sensitive map[string]bool
sensitiveKeys []string
regexSensitive []*regexp.Regexp
sensitiveRule []sensitiveRuleInfo
desensitization func(string) string
traceId string
}
type sensitiveRuleInfo struct {
threshold int
leftNum int
rightNum int
}
var (
writerMakers = make(map[string]func(*Config) Writer)
)
func RegisterWriterMaker(name string, f func(*Config) Writer) {
writerMakers[name] = f
}
func NewLogger(conf Config) *Logger {
if conf.Level == "" {
conf.Level = "info"
}
if conf.Truncations == "" {
conf.Truncations = "github.com/, golang.org/, /apigo.cc/"
}
if conf.Sensitive == "" {
conf.Sensitive = LogDefaultSensitive
}
if conf.SensitiveRule == "" {
conf.SensitiveRule = "12:4*4, 11:3*4, 7:2*2, 3:1*1, 2:1*0"
}
if conf.Name == "" {
conf.Name = GetDefaultName()
}
logger := Logger{
truncations: cast.Split(conf.Truncations, ","),
formatter: conf.Formatter,
}
if logger.formatter == nil {
logger.formatter = &JSONFormatter{}
}
if len(conf.Sensitive) > 0 {
logger.sensitive = make(map[string]bool)
ss := cast.Split(conf.Sensitive, ",")
for _, v := range ss {
f := fixField(v)
logger.sensitive[f] = true
logger.sensitiveKeys = append(logger.sensitiveKeys, f)
}
}
if len(conf.RegexSensitive) > 0 {
ss := cast.Split(conf.RegexSensitive, ",")
for _, v := range ss {
if r, err := regexp.Compile(v); err == nil {
logger.regexSensitive = append(logger.regexSensitive, r)
}
}
}
if len(conf.SensitiveRule) > 0 {
ss := cast.Split(conf.SensitiveRule, ",")
for _, v := range ss {
a1 := strings.SplitN(v, ":", 2)
if len(a1) == 2 {
a2 := strings.SplitN(a1[1], "*", 3)
if len(a2) == 2 {
threshold := cast.Int(a1[0])
leftNum := cast.Int(a2[0])
rightNum := cast.Int(a2[1])
if threshold >= 0 && threshold <= 100 && leftNum >= 0 && leftNum <= 100 && rightNum >= 0 && rightNum <= 100 {
logger.sensitiveRule = append(logger.sensitiveRule, sensitiveRuleInfo{
threshold: threshold,
leftNum: leftNum,
rightNum: rightNum,
})
}
}
}
}
}
switch strings.ToLower(conf.Level) {
case "debug":
logger.level = DEBUG
case "warning":
logger.level = WARNING
case "error":
logger.level = ERROR
default:
logger.level = INFO
}
if conf.File != "" && conf.File != "console" {
if strings.Contains(conf.File, "://") {
writerName := strings.SplitN(conf.File, "://", 2)[0]
if m, ok := writerMakers[writerName]; ok {
if w := m(&conf); w != nil {
logger.writer = w
writerLock.Lock()
cur := writers.Load().([]Writer)
newW := append(cur, w)
writers.Store(newW)
writerLock.Unlock()
Start()
}
}
} else {
if conf.SplitTag != "" {
filesLock.RLock()
logger.file = files[conf.File+conf.SplitTag]
filesLock.RUnlock()
if logger.file == nil {
logger.file = &FileWriter{
fileName: conf.File,
splitTag: conf.SplitTag,
}
filesLock.Lock()
files[conf.File+conf.SplitTag] = logger.file
filesLock.Unlock()
}
Start()
} else {
fp, err := os.OpenFile(conf.File, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err == nil {
logger.goLogger = log.New(fp, "", log.Ldate|log.Lmicroseconds)
}
}
}
}
logger.config = conf
return &logger
}
func (logger *Logger) Log(entry LogEntry) {
// 如果是自动生成的 LogType如 Debug/Info/Warning/Error 对应的类型),这里通常已经在方法内 fillBase 了
// 但对于像 discover.Log 这样直接调用 Log(entry) 的,需要在这里补全
if entry.GetBaseLog().LogTime == 0 {
logger.fillBase(entry, "")
}
// 自动补全调用栈
if st, ok := entry.(StackTraceable); ok {
if len(st.GetCallStacks()) == 0 {
st.SetCallStacks(getCallStacks(logger.truncations))
}
}
logger.asyncWrite(entry)
}
func (logger *Logger) asyncWrite(entry LogEntry) {
buf, err := logger.formatter.Format(entry, logger.sensitiveKeys)
if err == nil {
logger.writeBuf(buf)
}
PutEntry(entry)
}
func (logger *Logger) writeBuf(buf []byte) {
if writerRunning.Load() {
WriteAsync(logPayload{
buf: buf,
writer: logger.writer,
file: logger.file,
})
return
}
if logger.writer != nil {
logger.writer.Log(buf)
} else if logger.file != nil {
fmt.Println(Viewable(string(buf)))
} else if logger.goLogger == nil {
fmt.Println(Viewable(string(buf)))
} else {
logger.goLogger.Print(string(buf))
}
}
func (logger *Logger) fillBase(entry LogEntry, logType string) {
base := entry.GetBaseLog()
if base == nil {
return
}
base.LogName = logger.config.Name
if logType != "" {
base.LogType = logType
}
base.LogTime = time.Now().UnixNano()
base.TraceId = logger.traceId
base.ImageName = dockerImageName
base.ImageTag = dockerImageTag
base.ServerName = serverName
base.ServerIp = serverIp
}
func (logger *Logger) Debug(message string, extra ...any) {
if logger.CheckLevel(DEBUG) {
entry := GetEntry[DebugLog]()
logger.fillBase(entry, LogTypeDebug)
entry.Debug = message
if len(extra) > 0 {
cast.FillMap(&entry.Extra, extra)
}
logger.Log(entry)
}
}
func (logger *Logger) Info(message string, extra ...any) {
if logger.CheckLevel(INFO) {
entry := GetEntry[InfoLog]()
logger.fillBase(entry, LogTypeInfo)
entry.Info = message
if len(extra) > 0 {
cast.FillMap(&entry.Extra, extra)
}
logger.Log(entry)
}
}
func (logger *Logger) Warning(message string, extra ...any) {
if logger.CheckLevel(WARNING) {
entry := GetEntry[WarningLog]()
logger.fillBase(entry, LogTypeWarning)
entry.Warning = message
if len(extra) > 0 {
cast.FillMap(&entry.Extra, extra)
}
logger.Log(entry)
}
}
func (logger *Logger) Error(message string, extra ...any) {
if logger.CheckLevel(ERROR) {
entry := GetEntry[ErrorLog]()
logger.fillBase(entry, LogTypeError)
entry.Error = message
if len(extra) > 0 {
cast.FillMap(&entry.Extra, extra)
}
logger.Log(entry)
}
}
func (logger *Logger) SetLevel(level LevelType) {
logger.level = level
}
func (logger *Logger) SetDesensitization(f func(v string) string) {
logger.desensitization = f
}
func (logger *Logger) New(traceId string) *Logger {
newLogger := *logger
newLogger.traceId = traceId
return &newLogger
}
func (logger *Logger) GetTraceId() string {
return logger.traceId
}
func (logger *Logger) CheckLevel(logLevel LevelType) bool {
settedLevel := logger.level
if settedLevel == 0 {
settedLevel = INFO
}
return logLevel >= settedLevel
}