2020-12-21 17:22:45 +08:00
|
|
|
package starlog
|
|
|
|
|
|
|
|
|
|
import (
|
2026-03-19 16:37:57 +08:00
|
|
|
"context"
|
|
|
|
|
"errors"
|
2020-12-21 17:22:45 +08:00
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"math/rand"
|
2026-03-19 16:37:57 +08:00
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
2020-12-21 17:22:45 +08:00
|
|
|
"sync"
|
2026-03-19 16:37:57 +08:00
|
|
|
"sync/atomic"
|
2020-12-21 17:22:45 +08:00
|
|
|
"time"
|
|
|
|
|
|
2020-12-29 14:05:14 +08:00
|
|
|
"b612.me/starlog/colorable"
|
2020-12-21 17:22:45 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
LvDebug = iota
|
|
|
|
|
LvInfo
|
|
|
|
|
LvNotice
|
|
|
|
|
LvWarning
|
|
|
|
|
LvError
|
|
|
|
|
LvCritical
|
|
|
|
|
LvPanic
|
|
|
|
|
LvFatal
|
|
|
|
|
)
|
|
|
|
|
|
2026-03-19 16:37:57 +08:00
|
|
|
type ColorMode int
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
ColorModeOff ColorMode = iota
|
|
|
|
|
ColorModeFullLine
|
|
|
|
|
ColorModeLevelOnly
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type PendingDropPolicy int
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
PendingDropOldest PendingDropPolicy = iota
|
|
|
|
|
PendingDropNewest
|
|
|
|
|
PendingBlock
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type RedactFailMode int
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
RedactFailMaskAll RedactFailMode = iota
|
|
|
|
|
RedactFailOpen
|
|
|
|
|
RedactFailDrop
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
FieldTypeString = "string"
|
|
|
|
|
FieldTypeNumber = "number"
|
|
|
|
|
FieldTypeBool = "bool"
|
|
|
|
|
FieldTypeError = "error"
|
|
|
|
|
FieldTypeNil = "nil"
|
|
|
|
|
FieldTypeOther = "other"
|
|
|
|
|
)
|
|
|
|
|
|
2020-12-21 17:22:45 +08:00
|
|
|
var (
|
2026-03-19 16:37:57 +08:00
|
|
|
ErrAsyncHandlerPanic = errors.New("async handler panic")
|
|
|
|
|
ErrAsyncHandlerTimeout = errors.New("async handler timeout")
|
|
|
|
|
ErrAsyncQueueFull = errors.New("async queue full")
|
|
|
|
|
ErrPendingWriteDropped = errors.New("pending write dropped")
|
|
|
|
|
ErrInvalidLevel = errors.New("invalid log level")
|
|
|
|
|
ErrRedactionFailed = errors.New("redaction failed")
|
|
|
|
|
|
2020-12-21 17:22:45 +08:00
|
|
|
levels = map[int]string{
|
|
|
|
|
LvDebug: "DEBUG",
|
|
|
|
|
LvInfo: "INFO",
|
|
|
|
|
LvNotice: "NOTICE",
|
|
|
|
|
LvWarning: "WARNING",
|
|
|
|
|
LvError: "ERROR",
|
|
|
|
|
LvCritical: "CRITICAL",
|
|
|
|
|
LvPanic: "PANIC",
|
|
|
|
|
LvFatal: "FATAL",
|
|
|
|
|
}
|
2026-03-19 16:37:57 +08:00
|
|
|
levelAliases = map[string]int{
|
|
|
|
|
"debug": LvDebug,
|
|
|
|
|
"info": LvInfo,
|
|
|
|
|
"notice": LvNotice,
|
|
|
|
|
"warn": LvWarning,
|
|
|
|
|
"warning": LvWarning,
|
|
|
|
|
"err": LvError,
|
|
|
|
|
"error": LvError,
|
|
|
|
|
"critical": LvCritical,
|
|
|
|
|
"crit": LvCritical,
|
|
|
|
|
"panic": LvPanic,
|
|
|
|
|
"fatal": LvFatal,
|
|
|
|
|
}
|
2024-08-18 17:33:43 +08:00
|
|
|
stacks *starChanStack
|
2020-12-21 17:22:45 +08:00
|
|
|
stackStarted bool = false
|
2026-03-19 16:37:57 +08:00
|
|
|
stackStopChan chan struct{}
|
|
|
|
|
stackDoneChan chan struct{}
|
2021-09-23 10:39:17 +08:00
|
|
|
stackMu sync.Mutex
|
2026-03-19 16:37:57 +08:00
|
|
|
stackDrop uint64
|
|
|
|
|
stackAlert func(error, LogData)
|
|
|
|
|
stackAlertMu sync.RWMutex
|
|
|
|
|
stackFallback uint32 = 1
|
|
|
|
|
stackTimeout int64
|
|
|
|
|
|
|
|
|
|
writeErrCount uint64
|
|
|
|
|
writeErrHandler func(error, LogData)
|
|
|
|
|
writeErrMu sync.RWMutex
|
|
|
|
|
|
|
|
|
|
stdScreen io.Writer = colorable.NewColorableStdout()
|
|
|
|
|
errScreen io.Writer = colorable.NewColorableStderr()
|
2020-12-21 17:22:45 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type starlog struct {
|
2026-03-19 16:37:57 +08:00
|
|
|
mu *sync.Mutex
|
|
|
|
|
output io.Writer
|
|
|
|
|
minLevel int
|
|
|
|
|
errOutputLevel int
|
|
|
|
|
showFuncName bool
|
|
|
|
|
showThread bool
|
|
|
|
|
showLevel bool
|
|
|
|
|
showDeatilFile bool
|
|
|
|
|
showColor bool
|
|
|
|
|
switching bool
|
|
|
|
|
showStd bool
|
|
|
|
|
onlyColorLevel bool
|
|
|
|
|
autoAppendNewline bool
|
|
|
|
|
stopWriter bool
|
|
|
|
|
id string
|
|
|
|
|
name string
|
|
|
|
|
colorList map[int][]Attr
|
|
|
|
|
colorMe map[int]*Color
|
|
|
|
|
keywordColors map[string][]Attr
|
|
|
|
|
keywordOrder []string
|
|
|
|
|
keywordColorizers map[string]*Color
|
|
|
|
|
keywordMatcher *keywordMatcher
|
|
|
|
|
keywordMatchOptions KeywordMatchOptions
|
|
|
|
|
showFieldColor bool
|
|
|
|
|
fieldKeyColor []Attr
|
|
|
|
|
fieldTypeColors map[string][]Attr
|
|
|
|
|
fieldValueColors map[string][]Attr
|
|
|
|
|
entryHandler Handler
|
|
|
|
|
redactor Redactor
|
|
|
|
|
redactRules []RedactRule
|
|
|
|
|
redactFailMode RedactFailMode
|
|
|
|
|
redactMaskToken string
|
|
|
|
|
redactErrorCount uint64
|
|
|
|
|
formatter Formatter
|
|
|
|
|
sink Sink
|
|
|
|
|
pendingCond *sync.Cond
|
|
|
|
|
pendingWrites []string
|
|
|
|
|
pendingWriteLimit int
|
|
|
|
|
pendingDropPolicy PendingDropPolicy
|
|
|
|
|
pendingDropCount uint64
|
|
|
|
|
pendingBlockCount uint64
|
|
|
|
|
pendingPeakLen uint64
|
|
|
|
|
rateLimiter *rateLimiter
|
|
|
|
|
sampler *sampler
|
|
|
|
|
deduper *deduper
|
|
|
|
|
contextFields func(context.Context) Fields
|
|
|
|
|
entryHandlerTimeout time.Duration
|
2020-12-21 17:22:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type StarLogger struct {
|
|
|
|
|
thread string
|
2022-06-16 11:01:09 +08:00
|
|
|
handlerFunc func(LogData)
|
2020-12-21 17:22:45 +08:00
|
|
|
logcore *starlog
|
|
|
|
|
isStd bool
|
2026-03-19 16:37:57 +08:00
|
|
|
fields Fields
|
|
|
|
|
logErr error
|
|
|
|
|
logCtx context.Context
|
2020-12-21 17:22:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type logTransfer struct {
|
2022-06-16 11:01:09 +08:00
|
|
|
handlerFunc func(LogData)
|
|
|
|
|
LogData
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type LogData struct {
|
|
|
|
|
Name string
|
|
|
|
|
Log string
|
|
|
|
|
Colors []Attr
|
2020-12-21 17:22:45 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-19 16:37:57 +08:00
|
|
|
type PendingStats struct {
|
|
|
|
|
Limit int
|
|
|
|
|
Length int
|
|
|
|
|
PeakLength int
|
|
|
|
|
DropCount uint64
|
|
|
|
|
BlockCount uint64
|
|
|
|
|
Policy PendingDropPolicy
|
|
|
|
|
Switching bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type KeywordMatchOptions struct {
|
|
|
|
|
IgnoreCase bool
|
|
|
|
|
WholeWord bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Config is a logger core snapshot that can be read and applied atomically.
|
|
|
|
|
// Prefer GetConfig + UpdateConfig/ApplyConfig for multi-field configuration.
|
|
|
|
|
type Config struct {
|
|
|
|
|
Name string
|
|
|
|
|
Level int
|
|
|
|
|
StdErrLevel int
|
|
|
|
|
ShowFuncName bool
|
|
|
|
|
ShowFlag bool
|
|
|
|
|
ShowLevel bool
|
|
|
|
|
ShowOriginFile bool
|
|
|
|
|
ShowColor bool
|
|
|
|
|
OnlyColorLevel bool
|
|
|
|
|
ShowStd bool
|
|
|
|
|
StopWriter bool
|
|
|
|
|
AutoAppendNewline bool
|
|
|
|
|
Switching bool
|
|
|
|
|
LevelColors map[int][]Attr
|
|
|
|
|
KeywordColors map[string][]Attr
|
|
|
|
|
KeywordMatch KeywordMatchOptions
|
|
|
|
|
ShowFieldColor bool
|
|
|
|
|
FieldKeyColor []Attr
|
|
|
|
|
FieldTypeColors map[string][]Attr
|
|
|
|
|
FieldValueColors map[string][]Attr
|
|
|
|
|
|
|
|
|
|
EntryHandler Handler
|
|
|
|
|
EntryHandlerTimeout time.Duration
|
|
|
|
|
Formatter Formatter
|
|
|
|
|
Sink Sink
|
|
|
|
|
Writer io.Writer
|
|
|
|
|
|
|
|
|
|
PendingWriteLimit int
|
|
|
|
|
PendingDropPolicy PendingDropPolicy
|
|
|
|
|
|
|
|
|
|
Redactor Redactor
|
|
|
|
|
RedactRules []RedactRule
|
|
|
|
|
RedactFailMode RedactFailMode
|
|
|
|
|
RedactMaskToken string
|
|
|
|
|
|
|
|
|
|
RateLimit RateLimitConfig
|
|
|
|
|
Sampling SamplingConfig
|
|
|
|
|
Dedup DedupConfig
|
|
|
|
|
|
|
|
|
|
ContextFieldExtractor func(context.Context) Fields
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-21 17:22:45 +08:00
|
|
|
func newLogCore(out io.Writer) *starlog {
|
2026-03-19 16:37:57 +08:00
|
|
|
core := &starlog{
|
2020-12-21 17:22:45 +08:00
|
|
|
mu: &sync.Mutex{},
|
|
|
|
|
output: out,
|
2026-03-19 16:37:57 +08:00
|
|
|
minLevel: LvDebug,
|
2022-06-21 10:16:32 +08:00
|
|
|
errOutputLevel: LvError,
|
2020-12-21 17:22:45 +08:00
|
|
|
showFuncName: true,
|
|
|
|
|
showThread: true,
|
|
|
|
|
showLevel: true,
|
|
|
|
|
showStd: true,
|
|
|
|
|
showDeatilFile: true,
|
|
|
|
|
switching: false,
|
|
|
|
|
stopWriter: false,
|
|
|
|
|
showColor: true,
|
|
|
|
|
id: generateId(),
|
|
|
|
|
colorList: map[int][]Attr{
|
|
|
|
|
LvDebug: []Attr{FgWhite},
|
|
|
|
|
LvInfo: []Attr{FgGreen},
|
2022-06-16 11:01:09 +08:00
|
|
|
LvNotice: []Attr{FgCyan},
|
2020-12-21 17:22:45 +08:00
|
|
|
LvWarning: []Attr{FgYellow},
|
|
|
|
|
LvError: []Attr{FgMagenta},
|
|
|
|
|
LvCritical: []Attr{FgRed, Bold},
|
|
|
|
|
LvPanic: []Attr{FgRed, Bold},
|
|
|
|
|
LvFatal: []Attr{FgRed},
|
|
|
|
|
},
|
|
|
|
|
colorMe: map[int]*Color{
|
|
|
|
|
LvDebug: NewColor([]Attr{FgWhite}...),
|
|
|
|
|
LvInfo: NewColor([]Attr{FgGreen}...),
|
2022-06-16 11:01:09 +08:00
|
|
|
LvNotice: NewColor([]Attr{FgCyan}...),
|
2020-12-21 17:22:45 +08:00
|
|
|
LvWarning: NewColor([]Attr{FgYellow}...),
|
|
|
|
|
LvError: NewColor([]Attr{FgMagenta}...),
|
|
|
|
|
LvCritical: NewColor([]Attr{FgRed, Bold}...),
|
|
|
|
|
LvPanic: NewColor([]Attr{FgRed, Bold}...),
|
|
|
|
|
LvFatal: NewColor([]Attr{FgRed}...),
|
|
|
|
|
},
|
2026-03-19 16:37:57 +08:00
|
|
|
keywordColors: make(map[string][]Attr),
|
|
|
|
|
keywordOrder: nil,
|
|
|
|
|
keywordColorizers: nil,
|
|
|
|
|
keywordMatcher: nil,
|
|
|
|
|
showFieldColor: true,
|
|
|
|
|
fieldKeyColor: []Attr{FgHiBlue},
|
|
|
|
|
fieldTypeColors: map[string][]Attr{
|
|
|
|
|
FieldTypeString: []Attr{FgGreen},
|
|
|
|
|
FieldTypeNumber: []Attr{FgYellow},
|
|
|
|
|
FieldTypeBool: []Attr{FgMagenta},
|
|
|
|
|
FieldTypeError: []Attr{FgRed},
|
|
|
|
|
FieldTypeNil: []Attr{FgHiBlack},
|
|
|
|
|
FieldTypeOther: []Attr{FgCyan},
|
|
|
|
|
},
|
|
|
|
|
fieldValueColors: make(map[string][]Attr),
|
|
|
|
|
redactRules: make([]RedactRule, 0, 4),
|
|
|
|
|
redactFailMode: RedactFailMaskAll,
|
|
|
|
|
redactMaskToken: "[REDACTED]",
|
|
|
|
|
pendingWrites: make([]string, 0, 16),
|
|
|
|
|
pendingWriteLimit: 1024,
|
|
|
|
|
pendingDropPolicy: PendingDropOldest,
|
|
|
|
|
rateLimiter: newRateLimiter(),
|
|
|
|
|
sampler: newSampler(),
|
|
|
|
|
deduper: newDeduper(),
|
|
|
|
|
entryHandlerTimeout: 0,
|
2020-12-21 17:22:45 +08:00
|
|
|
}
|
2026-03-19 16:37:57 +08:00
|
|
|
core.rebuildKeywordCachesLocked()
|
|
|
|
|
core.pendingCond = sync.NewCond(core.mu)
|
|
|
|
|
return core
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ParseLevel(level string) (int, error) {
|
|
|
|
|
val := strings.TrimSpace(strings.ToLower(level))
|
|
|
|
|
if val == "" {
|
|
|
|
|
return 0, fmt.Errorf("%w: empty", ErrInvalidLevel)
|
|
|
|
|
}
|
|
|
|
|
if parsed, ok := levelAliases[val]; ok {
|
|
|
|
|
return parsed, nil
|
|
|
|
|
}
|
|
|
|
|
num, err := strconv.Atoi(val)
|
|
|
|
|
if err == nil {
|
|
|
|
|
return num, nil
|
|
|
|
|
}
|
|
|
|
|
return 0, fmt.Errorf("%w: %s", ErrInvalidLevel, level)
|
2020-12-21 17:22:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewStarlog(out io.Writer) *StarLogger {
|
|
|
|
|
return &StarLogger{
|
|
|
|
|
handlerFunc: nil,
|
|
|
|
|
thread: "MAN",
|
|
|
|
|
logcore: newLogCore(out),
|
|
|
|
|
isStd: false,
|
2026-03-19 16:37:57 +08:00
|
|
|
fields: nil,
|
|
|
|
|
logErr: nil,
|
|
|
|
|
logCtx: nil,
|
2020-12-21 17:22:45 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-21 10:16:32 +08:00
|
|
|
func (logger *StarLogger) StdErrLevel() int {
|
|
|
|
|
logger.logcore.mu.Lock()
|
|
|
|
|
defer logger.logcore.mu.Unlock()
|
|
|
|
|
return logger.logcore.errOutputLevel
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (logger *StarLogger) SetStdErrLevel(level int) {
|
|
|
|
|
logger.logcore.mu.Lock()
|
|
|
|
|
defer logger.logcore.mu.Unlock()
|
|
|
|
|
logger.logcore.errOutputLevel = level
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-21 17:22:45 +08:00
|
|
|
func (logger *StarLogger) NewFlag() *StarLogger {
|
|
|
|
|
return &StarLogger{
|
|
|
|
|
thread: getRandomFlag(false),
|
|
|
|
|
handlerFunc: logger.handlerFunc,
|
|
|
|
|
logcore: logger.logcore,
|
|
|
|
|
isStd: false,
|
2026-03-19 16:37:57 +08:00
|
|
|
fields: cloneFields(logger.fields),
|
|
|
|
|
logErr: logger.logErr,
|
|
|
|
|
logCtx: logger.logCtx,
|
2020-12-21 17:22:45 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
func (logger *StarLogger) SetNewRandomFlag() {
|
|
|
|
|
logger.thread = getRandomFlag(false)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-16 11:01:09 +08:00
|
|
|
func (logger *StarLogger) SetName(name string) {
|
|
|
|
|
logger.logcore.mu.Lock()
|
|
|
|
|
defer logger.logcore.mu.Unlock()
|
|
|
|
|
logger.logcore.name = name
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (logger *StarLogger) GetName() string {
|
2026-03-19 16:37:57 +08:00
|
|
|
logger.logcore.mu.Lock()
|
|
|
|
|
defer logger.logcore.mu.Unlock()
|
2022-06-16 11:01:09 +08:00
|
|
|
return logger.logcore.name
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-21 17:22:45 +08:00
|
|
|
func getRandomFlag(isMain bool) string {
|
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
|
if isMain {
|
|
|
|
|
return "MAN"
|
|
|
|
|
}
|
|
|
|
|
flag := "MAN"
|
|
|
|
|
for flag == "MAN" {
|
|
|
|
|
flag = string([]byte{uint8(rand.Intn(26) + 65), uint8(rand.Intn(26) + 65), uint8(rand.Intn(26) + 65)})
|
|
|
|
|
}
|
|
|
|
|
return flag
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func generateId() string {
|
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
|
return fmt.Sprintf("%dstar%db612%d", time.Now().UnixNano(), rand.Intn(1000000), rand.Intn(1000000))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func StartStacks() {
|
2021-09-23 10:39:17 +08:00
|
|
|
stackMu.Lock()
|
2020-12-21 17:22:45 +08:00
|
|
|
if stackStarted {
|
2021-09-23 10:39:17 +08:00
|
|
|
stackMu.Unlock()
|
2020-12-21 17:22:45 +08:00
|
|
|
return
|
|
|
|
|
}
|
2026-03-19 16:37:57 +08:00
|
|
|
stackStarted = true
|
|
|
|
|
stackStopChan = make(chan struct{})
|
|
|
|
|
stackDoneChan = make(chan struct{})
|
|
|
|
|
stacks = newStarChanStack(1024)
|
|
|
|
|
stopChan := stackStopChan
|
|
|
|
|
doneChan := stackDoneChan
|
|
|
|
|
stackMu.Unlock()
|
|
|
|
|
go func(stop <-chan struct{}, done chan struct{}) {
|
|
|
|
|
defer close(done)
|
2020-12-21 17:22:45 +08:00
|
|
|
defer func() {
|
2026-03-19 16:37:57 +08:00
|
|
|
stackMu.Lock()
|
2020-12-21 17:22:45 +08:00
|
|
|
stackStarted = false
|
2026-03-19 16:37:57 +08:00
|
|
|
stackMu.Unlock()
|
2020-12-21 17:22:45 +08:00
|
|
|
}()
|
|
|
|
|
for {
|
|
|
|
|
select {
|
2026-03-19 16:37:57 +08:00
|
|
|
case <-stop:
|
2020-12-21 17:22:45 +08:00
|
|
|
return
|
|
|
|
|
default:
|
|
|
|
|
}
|
2023-02-12 10:18:05 +08:00
|
|
|
poped, err := stacks.Pop()
|
|
|
|
|
if err != nil {
|
2026-03-19 16:37:57 +08:00
|
|
|
if errors.Is(err, io.EOF) {
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-02-12 10:18:05 +08:00
|
|
|
return
|
2020-12-21 17:22:45 +08:00
|
|
|
}
|
2026-03-19 16:37:57 +08:00
|
|
|
val, ok := poped.(logTransfer)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2020-12-21 17:22:45 +08:00
|
|
|
if val.handlerFunc != nil {
|
2026-03-19 16:37:57 +08:00
|
|
|
invokeAsyncHandlerSafely(val.handlerFunc, val.LogData)
|
2020-12-21 17:22:45 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-19 16:37:57 +08:00
|
|
|
}(stopChan, doneChan)
|
2020-12-21 17:22:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func StopStacks() {
|
2026-03-19 16:37:57 +08:00
|
|
|
stackMu.Lock()
|
2020-12-21 17:22:45 +08:00
|
|
|
if !stackStarted {
|
2026-03-19 16:37:57 +08:00
|
|
|
stackMu.Unlock()
|
2020-12-21 17:22:45 +08:00
|
|
|
return
|
|
|
|
|
}
|
2026-03-19 16:37:57 +08:00
|
|
|
stopChan := stackStopChan
|
|
|
|
|
doneChan := stackDoneChan
|
|
|
|
|
current := stacks
|
|
|
|
|
stackStopChan = nil
|
|
|
|
|
stackDoneChan = nil
|
|
|
|
|
stackMu.Unlock()
|
|
|
|
|
|
|
|
|
|
if stopChan != nil {
|
|
|
|
|
func() {
|
|
|
|
|
defer func() {
|
|
|
|
|
recover()
|
|
|
|
|
}()
|
|
|
|
|
close(stopChan)
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
if current != nil {
|
|
|
|
|
_ = current.Close()
|
|
|
|
|
}
|
|
|
|
|
if doneChan != nil {
|
|
|
|
|
<-doneChan
|
|
|
|
|
}
|
2020-12-21 17:22:45 +08:00
|
|
|
}
|
2021-09-23 10:44:56 +08:00
|
|
|
|
|
|
|
|
func Stop() {
|
|
|
|
|
StopStacks()
|
2021-11-12 16:03:34 +08:00
|
|
|
}
|
2026-03-19 16:37:57 +08:00
|
|
|
|
|
|
|
|
func SetAsyncErrorHandler(alert func(error, LogData)) {
|
|
|
|
|
stackAlertMu.Lock()
|
|
|
|
|
defer stackAlertMu.Unlock()
|
|
|
|
|
stackAlert = alert
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func SetAsyncFallbackToSync(enable bool) {
|
|
|
|
|
if enable {
|
|
|
|
|
atomic.StoreUint32(&stackFallback, 1)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
atomic.StoreUint32(&stackFallback, 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetAsyncFallbackToSync() bool {
|
|
|
|
|
return atomic.LoadUint32(&stackFallback) == 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func SetAsyncHandlerTimeout(timeout time.Duration) {
|
|
|
|
|
if timeout < 0 {
|
|
|
|
|
timeout = 0
|
|
|
|
|
}
|
|
|
|
|
atomic.StoreInt64(&stackTimeout, int64(timeout))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetAsyncHandlerTimeout() time.Duration {
|
|
|
|
|
return time.Duration(atomic.LoadInt64(&stackTimeout))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetAsyncDropCount() uint64 {
|
|
|
|
|
return atomic.LoadUint64(&stackDrop)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func reportAsyncDrop(err error, data LogData) {
|
|
|
|
|
atomic.AddUint64(&stackDrop, 1)
|
|
|
|
|
stackAlertMu.RLock()
|
|
|
|
|
alert := stackAlert
|
|
|
|
|
stackAlertMu.RUnlock()
|
|
|
|
|
if alert != nil {
|
|
|
|
|
func() {
|
|
|
|
|
defer func() {
|
|
|
|
|
recover()
|
|
|
|
|
}()
|
|
|
|
|
alert(err, data)
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func invokeAsyncHandlerSafely(handler func(LogData), data LogData) bool {
|
|
|
|
|
if handler == nil {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
timeout := GetAsyncHandlerTimeout()
|
|
|
|
|
if timeout <= 0 {
|
|
|
|
|
return invokeAsyncHandlerDirect(handler, data)
|
|
|
|
|
}
|
|
|
|
|
done := make(chan bool, 1)
|
|
|
|
|
go func() {
|
|
|
|
|
done <- invokeAsyncHandlerDirect(handler, data)
|
|
|
|
|
}()
|
|
|
|
|
select {
|
|
|
|
|
case ok := <-done:
|
|
|
|
|
return ok
|
|
|
|
|
case <-time.After(timeout):
|
|
|
|
|
reportAsyncDrop(ErrAsyncHandlerTimeout, data)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func invokeAsyncHandlerDirect(handler func(LogData), data LogData) (ok bool) {
|
|
|
|
|
defer func() {
|
|
|
|
|
if panicErr := recover(); panicErr != nil {
|
|
|
|
|
reportAsyncDrop(fmt.Errorf("%w: %v", ErrAsyncHandlerPanic, panicErr), data)
|
|
|
|
|
ok = false
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
handler(data)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func SetWriteErrorHandler(alert func(error, LogData)) {
|
|
|
|
|
writeErrMu.Lock()
|
|
|
|
|
defer writeErrMu.Unlock()
|
|
|
|
|
writeErrHandler = alert
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetWriteErrorCount() uint64 {
|
|
|
|
|
return atomic.LoadUint64(&writeErrCount)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func reportWriteError(err error, data LogData) {
|
|
|
|
|
if err == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
atomic.AddUint64(&writeErrCount, 1)
|
|
|
|
|
writeErrMu.RLock()
|
|
|
|
|
alert := writeErrHandler
|
|
|
|
|
writeErrMu.RUnlock()
|
|
|
|
|
if alert != nil {
|
|
|
|
|
func() {
|
|
|
|
|
defer func() {
|
|
|
|
|
recover()
|
|
|
|
|
}()
|
|
|
|
|
alert(err, data)
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func resetAsyncMetricsForTest() {
|
|
|
|
|
atomic.StoreUint64(&stackDrop, 0)
|
|
|
|
|
SetAsyncErrorHandler(nil)
|
|
|
|
|
SetAsyncFallbackToSync(true)
|
|
|
|
|
SetAsyncHandlerTimeout(0)
|
|
|
|
|
atomic.StoreUint64(&writeErrCount, 0)
|
|
|
|
|
SetWriteErrorHandler(nil)
|
|
|
|
|
}
|