1066 lines
29 KiB
Go
1066 lines
29 KiB
Go
package starlog
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
func generateCoreLogStr(skip int, logstr string) string {
|
|
var line int = 0
|
|
var funcname, fileName string
|
|
now := time.Now()
|
|
|
|
pc, fName, codeln, ok := runtime.Caller(skip)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
line = codeln
|
|
funcname = runtime.FuncForPC(pc).Name()
|
|
funcname = filepath.Ext(funcname)
|
|
funcname = strings.TrimPrefix(funcname, ".")
|
|
fileName = filepath.Base(fName)
|
|
|
|
y, m, d := now.Date()
|
|
h, i, s := now.Clock()
|
|
micro := now.Nanosecond() / 1e3
|
|
logStr := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%06d", y, m, d, h, i, s, micro)
|
|
logStr += " " + fileName + ":" + strconv.Itoa(line)
|
|
logStr += " <" + funcname + ">"
|
|
logStr += " " + logstr
|
|
return logStr
|
|
}
|
|
|
|
func (logger *starlog) levelName(level int) string {
|
|
levelName, ok := levels[level]
|
|
if !ok {
|
|
return strconv.Itoa(level)
|
|
}
|
|
return levelName
|
|
}
|
|
|
|
func (logger *starlog) levelColor(level int) *Color {
|
|
color, ok := logger.colorMe[level]
|
|
if ok && color != nil {
|
|
return color
|
|
}
|
|
return NewColor(FgWhite)
|
|
}
|
|
|
|
func (logger *starlog) levelAttrs(level int) []Attr {
|
|
attrs, ok := logger.colorList[level]
|
|
if !ok {
|
|
return []Attr{FgWhite}
|
|
}
|
|
cloned := make([]Attr, len(attrs))
|
|
copy(cloned, attrs)
|
|
return cloned
|
|
}
|
|
|
|
func cloneLevelAttrsMap(source map[int][]Attr) map[int][]Attr {
|
|
if len(source) == 0 {
|
|
return map[int][]Attr{}
|
|
}
|
|
cloned := make(map[int][]Attr, len(source))
|
|
for level, attrs := range source {
|
|
attrCopy := make([]Attr, len(attrs))
|
|
copy(attrCopy, attrs)
|
|
cloned[level] = attrCopy
|
|
}
|
|
return cloned
|
|
}
|
|
|
|
func cloneLevelColorMap(source map[int][]Attr) map[int]*Color {
|
|
if len(source) == 0 {
|
|
return map[int]*Color{}
|
|
}
|
|
cloned := make(map[int]*Color, len(source))
|
|
for level, attrs := range source {
|
|
cloned[level] = NewColor(attrs...)
|
|
}
|
|
return cloned
|
|
}
|
|
|
|
func cloneStringSlice(source []string) []string {
|
|
if len(source) == 0 {
|
|
return nil
|
|
}
|
|
cloned := make([]string, len(source))
|
|
copy(cloned, source)
|
|
return cloned
|
|
}
|
|
|
|
func cloneKeywordColorizers(source map[string]*Color) map[string]*Color {
|
|
if len(source) == 0 {
|
|
return nil
|
|
}
|
|
cloned := make(map[string]*Color, len(source))
|
|
for key, colorizer := range source {
|
|
cloned[key] = colorizer
|
|
}
|
|
return cloned
|
|
}
|
|
|
|
type keywordPattern struct {
|
|
keyword string
|
|
keywordLower string
|
|
byteLen int
|
|
colorizer *Color
|
|
}
|
|
|
|
type keywordMatcher struct {
|
|
byFirstRune map[rune][]keywordPattern
|
|
byFirstRuneFold map[rune][]keywordPattern
|
|
}
|
|
|
|
func buildKeywordMatcher(order []string, colorizers map[string]*Color) *keywordMatcher {
|
|
if len(order) == 0 {
|
|
return nil
|
|
}
|
|
matcher := &keywordMatcher{
|
|
byFirstRune: make(map[rune][]keywordPattern),
|
|
byFirstRuneFold: make(map[rune][]keywordPattern),
|
|
}
|
|
for _, keyword := range order {
|
|
if keyword == "" {
|
|
continue
|
|
}
|
|
colorizer := colorizers[keyword]
|
|
if colorizer == nil {
|
|
continue
|
|
}
|
|
firstRune, _ := utf8.DecodeRuneInString(keyword)
|
|
if firstRune == utf8.RuneError {
|
|
continue
|
|
}
|
|
pattern := keywordPattern{
|
|
keyword: keyword,
|
|
keywordLower: strings.ToLower(keyword),
|
|
byteLen: len(keyword),
|
|
colorizer: colorizer,
|
|
}
|
|
matcher.byFirstRune[firstRune] = append(matcher.byFirstRune[firstRune], pattern)
|
|
matcher.byFirstRuneFold[unicode.ToLower(firstRune)] = append(matcher.byFirstRuneFold[unicode.ToLower(firstRune)], pattern)
|
|
}
|
|
if len(matcher.byFirstRune) == 0 {
|
|
return nil
|
|
}
|
|
return matcher
|
|
}
|
|
|
|
func (logger *starlog) rebuildKeywordCachesLocked() {
|
|
if logger.keywordColors == nil {
|
|
logger.keywordColors = make(map[string][]Attr)
|
|
}
|
|
keywords := make([]string, 0, len(logger.keywordColors))
|
|
var colorizers map[string]*Color
|
|
for keyword, attrs := range logger.keywordColors {
|
|
if keyword == "" {
|
|
continue
|
|
}
|
|
keywords = append(keywords, keyword)
|
|
if len(attrs) > 0 {
|
|
if colorizers == nil {
|
|
colorizers = make(map[string]*Color, len(logger.keywordColors))
|
|
}
|
|
colorizers[keyword] = NewColor(attrs...)
|
|
}
|
|
}
|
|
sort.Slice(keywords, func(i, j int) bool {
|
|
return len(keywords[i]) > len(keywords[j])
|
|
})
|
|
if len(keywords) == 0 {
|
|
keywords = nil
|
|
}
|
|
logger.keywordOrder = keywords
|
|
logger.keywordColorizers = colorizers
|
|
logger.keywordMatcher = buildKeywordMatcher(keywords, colorizers)
|
|
}
|
|
|
|
func (logger *starlog) snapshotForBuildLocked() *starlog {
|
|
colorAttrs := cloneLevelAttrsMap(logger.colorList)
|
|
return &starlog{
|
|
minLevel: logger.minLevel,
|
|
errOutputLevel: logger.errOutputLevel,
|
|
showFuncName: logger.showFuncName,
|
|
showThread: logger.showThread,
|
|
showLevel: logger.showLevel,
|
|
showDeatilFile: logger.showDeatilFile,
|
|
showColor: logger.showColor,
|
|
onlyColorLevel: logger.onlyColorLevel,
|
|
autoAppendNewline: logger.autoAppendNewline,
|
|
stopWriter: logger.stopWriter,
|
|
name: logger.name,
|
|
colorList: colorAttrs,
|
|
colorMe: cloneLevelColorMap(colorAttrs),
|
|
keywordColors: nil,
|
|
keywordOrder: nil,
|
|
keywordColorizers: nil,
|
|
keywordMatcher: logger.keywordMatcher,
|
|
keywordMatchOptions: logger.keywordMatchOptions,
|
|
showFieldColor: logger.showFieldColor,
|
|
fieldKeyColor: cloneAttrs(logger.fieldKeyColor),
|
|
fieldTypeColors: cloneColorMap(logger.fieldTypeColors),
|
|
fieldValueColors: cloneColorMap(logger.fieldValueColors),
|
|
entryHandler: logger.entryHandler,
|
|
redactor: logger.redactor,
|
|
redactRules: cloneRedactRules(logger.redactRules),
|
|
redactFailMode: logger.redactFailMode,
|
|
redactMaskToken: logger.redactMaskToken,
|
|
entryHandlerTimeout: logger.entryHandlerTimeout,
|
|
formatter: logger.formatter,
|
|
contextFields: logger.contextFields,
|
|
}
|
|
}
|
|
|
|
func (logger *starlog) formatTime(now time.Time) string {
|
|
y, m, d := now.Date()
|
|
h, i, s := now.Clock()
|
|
micro := now.Nanosecond() / 1e3
|
|
return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%06d", y, m, d, h, i, s, micro)
|
|
}
|
|
|
|
func (logger *starlog) buildMeta(fileName string, line int, funcname string, thread string) string {
|
|
parts := make([]string, 0, 3)
|
|
if logger.showDeatilFile {
|
|
parts = append(parts, fileName+":"+strconv.Itoa(line))
|
|
}
|
|
if logger.showFuncName {
|
|
parts = append(parts, "<"+funcname+">")
|
|
}
|
|
if logger.showThread {
|
|
parts = append(parts, "|"+thread+"|")
|
|
}
|
|
return strings.Join(parts, " ")
|
|
}
|
|
|
|
func (logger *starlog) composeLine(timestamp string, meta string, levelTag string, message string) string {
|
|
parts := make([]string, 0, 4)
|
|
parts = append(parts, timestamp)
|
|
if meta != "" {
|
|
parts = append(parts, meta)
|
|
}
|
|
if logger.showLevel && levelTag != "" {
|
|
parts = append(parts, levelTag)
|
|
}
|
|
parts = append(parts, message)
|
|
return strings.Join(parts, " ")
|
|
}
|
|
|
|
func appendNewlineIfNeeded(text string, autoAppend bool) string {
|
|
if !autoAppend || text == "" {
|
|
return text
|
|
}
|
|
if strings.HasSuffix(text, "\n") {
|
|
return text
|
|
}
|
|
return text + "\n"
|
|
}
|
|
|
|
func (logger *starlog) highlightKeywords(text string) string {
|
|
if logger.keywordMatcher != nil {
|
|
if highlighted, changed := logger.keywordMatcher.highlight(text, logger.keywordMatchOptions); changed {
|
|
return highlighted
|
|
}
|
|
return text
|
|
}
|
|
if len(logger.keywordColors) == 0 {
|
|
return text
|
|
}
|
|
keywords := logger.keywordOrder
|
|
if len(keywords) == 0 {
|
|
keywords = make([]string, 0, len(logger.keywordColors))
|
|
for keyword := range logger.keywordColors {
|
|
if keyword == "" {
|
|
continue
|
|
}
|
|
keywords = append(keywords, keyword)
|
|
}
|
|
if len(keywords) == 0 {
|
|
return text
|
|
}
|
|
sort.Slice(keywords, func(i, j int) bool {
|
|
return len(keywords[i]) > len(keywords[j])
|
|
})
|
|
}
|
|
|
|
highlighted := text
|
|
for _, keyword := range keywords {
|
|
attrs := logger.keywordColors[keyword]
|
|
if len(attrs) == 0 {
|
|
continue
|
|
}
|
|
colorizer := logger.keywordColorizers[keyword]
|
|
highlighted = highlightKeywordText(highlighted, keyword, attrs, logger.keywordMatchOptions, colorizer)
|
|
}
|
|
return highlighted
|
|
}
|
|
|
|
func isWordRune(r rune) bool {
|
|
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
|
}
|
|
|
|
func hasWordBoundary(text string, start int, end int) bool {
|
|
if start < 0 || end < start || end > len(text) {
|
|
return false
|
|
}
|
|
leftBoundary := true
|
|
if start > 0 {
|
|
r, _ := utf8.DecodeLastRuneInString(text[:start])
|
|
leftBoundary = !isWordRune(r)
|
|
}
|
|
rightBoundary := true
|
|
if end < len(text) {
|
|
r, _ := utf8.DecodeRuneInString(text[end:])
|
|
rightBoundary = !isWordRune(r)
|
|
}
|
|
return leftBoundary && rightBoundary
|
|
}
|
|
|
|
func (matcher *keywordMatcher) highlight(text string, opts KeywordMatchOptions) (string, bool) {
|
|
if matcher == nil || text == "" {
|
|
return text, false
|
|
}
|
|
|
|
var builder strings.Builder
|
|
builder.Grow(len(text) + 32)
|
|
last := 0
|
|
changed := false
|
|
|
|
for idx := 0; idx < len(text); {
|
|
r, size := utf8.DecodeRuneInString(text[idx:])
|
|
if size <= 0 {
|
|
size = 1
|
|
}
|
|
next := idx + size
|
|
|
|
var patterns []keywordPattern
|
|
if opts.IgnoreCase {
|
|
patterns = matcher.byFirstRuneFold[unicode.ToLower(r)]
|
|
} else {
|
|
patterns = matcher.byFirstRune[r]
|
|
}
|
|
|
|
matched := false
|
|
for _, pattern := range patterns {
|
|
matchEnd := idx + pattern.byteLen
|
|
if matchEnd > len(text) {
|
|
continue
|
|
}
|
|
candidate := text[idx:matchEnd]
|
|
|
|
ok := false
|
|
if opts.IgnoreCase {
|
|
ok = strings.EqualFold(candidate, pattern.keyword)
|
|
} else {
|
|
ok = candidate == pattern.keyword
|
|
}
|
|
if !ok {
|
|
continue
|
|
}
|
|
if opts.WholeWord && !hasWordBoundary(text, idx, matchEnd) {
|
|
continue
|
|
}
|
|
|
|
builder.WriteString(text[last:idx])
|
|
if pattern.colorizer != nil {
|
|
builder.WriteString(pattern.colorizer.Sprint(candidate))
|
|
} else {
|
|
builder.WriteString(candidate)
|
|
}
|
|
last = matchEnd
|
|
idx = matchEnd
|
|
matched = true
|
|
changed = true
|
|
break
|
|
}
|
|
|
|
if matched {
|
|
continue
|
|
}
|
|
|
|
if r == utf8.RuneError && size == 1 {
|
|
idx++
|
|
continue
|
|
}
|
|
idx = next
|
|
}
|
|
|
|
if !changed {
|
|
return text, false
|
|
}
|
|
builder.WriteString(text[last:])
|
|
return builder.String(), true
|
|
}
|
|
|
|
func highlightKeywordText(text string, keyword string, attrs []Attr, opts KeywordMatchOptions, colorizer *Color) string {
|
|
if text == "" || keyword == "" || len(attrs) == 0 {
|
|
return text
|
|
}
|
|
if colorizer == nil {
|
|
colorizer = NewColor(attrs...)
|
|
}
|
|
if !opts.IgnoreCase && !opts.WholeWord {
|
|
return strings.ReplaceAll(text, keyword, colorizer.Sprint(keyword))
|
|
}
|
|
|
|
var builder strings.Builder
|
|
builder.Grow(len(text) + 32)
|
|
changed := false
|
|
last := 0
|
|
|
|
for idx := 0; idx < len(text); {
|
|
r, size := utf8.DecodeRuneInString(text[idx:])
|
|
if size <= 0 {
|
|
size = 1
|
|
}
|
|
next := idx + size
|
|
|
|
matchEnd := idx + len(keyword)
|
|
if matchEnd <= len(text) {
|
|
candidate := text[idx:matchEnd]
|
|
matched := false
|
|
if opts.IgnoreCase {
|
|
matched = strings.EqualFold(candidate, keyword)
|
|
} else {
|
|
matched = candidate == keyword
|
|
}
|
|
if matched {
|
|
if !opts.WholeWord || hasWordBoundary(text, idx, matchEnd) {
|
|
builder.WriteString(text[last:idx])
|
|
builder.WriteString(colorizer.Sprint(candidate))
|
|
last = matchEnd
|
|
idx = matchEnd
|
|
changed = true
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
if r == utf8.RuneError && size == 1 {
|
|
idx++
|
|
continue
|
|
}
|
|
idx = next
|
|
}
|
|
|
|
if !changed {
|
|
return text
|
|
}
|
|
builder.WriteString(text[last:])
|
|
return builder.String()
|
|
}
|
|
|
|
func (logger *starlog) formatPlainFromEntry(entry *Entry, defaultLine string) string {
|
|
if logger.formatter == nil {
|
|
return defaultLine
|
|
}
|
|
formatted, err := logger.formatter.Format(entry)
|
|
if err != nil || len(formatted) == 0 {
|
|
return defaultLine
|
|
}
|
|
return string(formatted)
|
|
}
|
|
|
|
func (logger *starlog) renderEntryMessage(entry *Entry) string {
|
|
if entry == nil {
|
|
return ""
|
|
}
|
|
parts := make([]string, 0, 3)
|
|
if entry.Message != "" {
|
|
parts = append(parts, entry.Message)
|
|
}
|
|
if entry.Err != nil {
|
|
parts = append(parts, "error="+entry.Err.Error())
|
|
}
|
|
fieldText := renderFields(entry.Fields)
|
|
if fieldText != "" {
|
|
parts = append(parts, fieldText)
|
|
}
|
|
return strings.Join(parts, " ")
|
|
}
|
|
|
|
func fieldTypeName(value interface{}) string {
|
|
switch value.(type) {
|
|
case nil:
|
|
return FieldTypeNil
|
|
case bool:
|
|
return FieldTypeBool
|
|
case string:
|
|
return FieldTypeString
|
|
case error:
|
|
return FieldTypeError
|
|
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
|
return FieldTypeNumber
|
|
default:
|
|
return FieldTypeOther
|
|
}
|
|
}
|
|
|
|
func (logger *starlog) renderDisplayFieldValue(key string, value interface{}) string {
|
|
raw := fmt.Sprintf("%v", value)
|
|
if !logger.showFieldColor {
|
|
return raw
|
|
}
|
|
if attrs, ok := logger.fieldValueColors[key]; ok && len(attrs) > 0 {
|
|
return NewColor(attrs...).Sprint(raw)
|
|
}
|
|
typeName := fieldTypeName(value)
|
|
if attrs, ok := logger.fieldTypeColors[typeName]; ok && len(attrs) > 0 {
|
|
return NewColor(attrs...).Sprint(raw)
|
|
}
|
|
return raw
|
|
}
|
|
|
|
func (logger *starlog) renderDisplayFields(fields Fields) string {
|
|
if len(fields) == 0 {
|
|
return ""
|
|
}
|
|
keys := make([]string, 0, len(fields))
|
|
for key := range fields {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
items := make([]string, 0, len(keys))
|
|
for _, key := range keys {
|
|
keyText := key
|
|
if logger.showFieldColor && len(logger.fieldKeyColor) > 0 {
|
|
keyText = NewColor(logger.fieldKeyColor...).Sprint(keyText)
|
|
}
|
|
valueText := logger.renderDisplayFieldValue(key, fields[key])
|
|
items = append(items, keyText+"="+valueText)
|
|
}
|
|
return strings.Join(items, " ")
|
|
}
|
|
|
|
func (logger *starlog) renderDisplayMessage(entry *Entry) string {
|
|
if entry == nil {
|
|
return ""
|
|
}
|
|
parts := make([]string, 0, 2)
|
|
base := entry.Message
|
|
if entry.Err != nil {
|
|
if base != "" {
|
|
base += " "
|
|
}
|
|
base += "error=" + entry.Err.Error()
|
|
}
|
|
if base != "" {
|
|
parts = append(parts, logger.highlightKeywords(base))
|
|
}
|
|
fieldText := logger.renderDisplayFields(entry.Fields)
|
|
if fieldText != "" {
|
|
parts = append(parts, fieldText)
|
|
}
|
|
return strings.Join(parts, " ")
|
|
}
|
|
|
|
func (logger *starlog) flushPendingWritesLocked(name string, colors []Attr) {
|
|
if logger.switching {
|
|
return
|
|
}
|
|
for len(logger.pendingWrites) > 0 {
|
|
if logger.switching {
|
|
return
|
|
}
|
|
logStr := logger.pendingWrites[0]
|
|
logger.pendingWrites = logger.pendingWrites[1:]
|
|
logger.signalPendingCondLocked()
|
|
if err := logger.writeDirect(logStr); err != nil {
|
|
reportWriteError(err, LogData{
|
|
Name: name,
|
|
Log: logStr,
|
|
Colors: colors,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func (logger *starlog) signalPendingCondLocked() {
|
|
if logger.pendingCond != nil {
|
|
logger.pendingCond.Broadcast()
|
|
}
|
|
}
|
|
|
|
func (logger *starlog) updatePendingPeakLocked() {
|
|
current := uint64(len(logger.pendingWrites))
|
|
for {
|
|
peak := atomic.LoadUint64(&logger.pendingPeakLen)
|
|
if current <= peak {
|
|
return
|
|
}
|
|
if atomic.CompareAndSwapUint64(&logger.pendingPeakLen, peak, current) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (logger *starlog) writeDirect(logStr string) error {
|
|
if logger.sink != nil {
|
|
return logger.sink.Write([]byte(logStr))
|
|
}
|
|
if logger.output == nil {
|
|
return nil
|
|
}
|
|
_, err := logger.output.Write([]byte(logStr))
|
|
return err
|
|
}
|
|
|
|
func (logger *starlog) enqueuePendingWriteLocked(logStr string, data LogData) bool {
|
|
for logger.switching && logger.pendingWriteLimit > 0 && len(logger.pendingWrites) >= logger.pendingWriteLimit {
|
|
switch logger.pendingDropPolicy {
|
|
case PendingDropNewest:
|
|
atomic.AddUint64(&logger.pendingDropCount, 1)
|
|
reportWriteError(ErrPendingWriteDropped, data)
|
|
return true
|
|
case PendingBlock:
|
|
atomic.AddUint64(&logger.pendingBlockCount, 1)
|
|
if logger.pendingCond == nil {
|
|
atomic.AddUint64(&logger.pendingDropCount, 1)
|
|
reportWriteError(ErrPendingWriteDropped, data)
|
|
return true
|
|
}
|
|
logger.pendingCond.Wait()
|
|
default:
|
|
atomic.AddUint64(&logger.pendingDropCount, 1)
|
|
dropData := data
|
|
dropData.Log = logger.pendingWrites[0]
|
|
logger.pendingWrites = logger.pendingWrites[1:]
|
|
logger.signalPendingCondLocked()
|
|
reportWriteError(ErrPendingWriteDropped, dropData)
|
|
}
|
|
}
|
|
if !logger.switching {
|
|
return false
|
|
}
|
|
logger.pendingWrites = append(logger.pendingWrites, logStr)
|
|
logger.updatePendingPeakLocked()
|
|
return true
|
|
}
|
|
|
|
func (logger *starlog) invokeEntryHandler(handler Handler, timeout time.Duration, entry *Entry, data LogData) {
|
|
if handler == nil || entry == nil {
|
|
return
|
|
}
|
|
handlerCtx := entry.Context
|
|
if handlerCtx == nil {
|
|
handlerCtx = context.Background()
|
|
}
|
|
run := func() {
|
|
defer func() {
|
|
if panicErr := recover(); panicErr != nil {
|
|
reportAsyncDrop(fmt.Errorf("%w: %v", ErrAsyncHandlerPanic, panicErr), data)
|
|
}
|
|
}()
|
|
if err := handler.Handle(handlerCtx, entry); err != nil {
|
|
reportWriteError(err, data)
|
|
}
|
|
}
|
|
if timeout <= 0 {
|
|
run()
|
|
return
|
|
}
|
|
done := make(chan struct{}, 1)
|
|
go func() {
|
|
run()
|
|
done <- struct{}{}
|
|
}()
|
|
select {
|
|
case <-done:
|
|
case <-time.After(timeout):
|
|
reportAsyncDrop(ErrAsyncHandlerTimeout, data)
|
|
}
|
|
}
|
|
|
|
func (logger *starlog) enqueueAsyncTransfer(transfer logTransfer, fallbackSync bool) {
|
|
StartStacks()
|
|
if stacks == nil {
|
|
reportAsyncDrop(io.ErrClosedPipe, transfer.LogData)
|
|
if fallbackSync && GetAsyncFallbackToSync() {
|
|
invokeAsyncHandlerSafely(transfer.handlerFunc, transfer.LogData)
|
|
}
|
|
return
|
|
}
|
|
if err := stacks.TryPush(transfer); err != nil {
|
|
if errors.Is(err, errStackFull) {
|
|
reportAsyncDrop(ErrAsyncQueueFull, transfer.LogData)
|
|
} else {
|
|
reportAsyncDrop(err, transfer.LogData)
|
|
}
|
|
if fallbackSync && GetAsyncFallbackToSync() {
|
|
invokeAsyncHandlerSafely(transfer.handlerFunc, transfer.LogData)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (logger *starlog) build(thread string, isStd bool, isShow bool, handler func(data LogData), level int, logDetail string, fields Fields, logErr error, ctx context.Context) {
|
|
logger.mu.Lock()
|
|
snapshot := logger.snapshotForBuildLocked()
|
|
logger.mu.Unlock()
|
|
if level < snapshot.minLevel {
|
|
return
|
|
}
|
|
|
|
var skip, line int = 3, 0
|
|
var funcname, fileName string
|
|
now := time.Now()
|
|
if isStd {
|
|
skip++
|
|
}
|
|
if snapshot.showDeatilFile || snapshot.showFuncName {
|
|
pc, fName, codeln, ok := runtime.Caller(skip)
|
|
if !ok {
|
|
return
|
|
}
|
|
line = codeln
|
|
funcname = runtime.FuncForPC(pc).Name()
|
|
funcname = filepath.Ext(funcname)
|
|
funcname = strings.TrimPrefix(funcname, ".")
|
|
fileName = filepath.Base(fName)
|
|
}
|
|
|
|
levelName := snapshot.levelName(level)
|
|
levelTag := "[" + levelName + "]"
|
|
timestamp := snapshot.formatTime(now)
|
|
meta := snapshot.buildMeta(fileName, line, funcname, thread)
|
|
|
|
contextFields := Fields(nil)
|
|
if ctx != nil && snapshot.contextFields != nil {
|
|
contextFields = snapshot.contextFields(ctx)
|
|
}
|
|
mergedFields := mergeFields(fields, contextFields)
|
|
|
|
entry := &Entry{
|
|
Time: now,
|
|
Level: level,
|
|
LevelName: levelName,
|
|
LoggerName: snapshot.name,
|
|
Thread: thread,
|
|
File: fileName,
|
|
Line: line,
|
|
Func: funcname,
|
|
Message: logDetail,
|
|
Fields: mergedFields,
|
|
Err: logErr,
|
|
Context: ctx,
|
|
}
|
|
if !logger.applyRedaction(snapshot, entry) {
|
|
return
|
|
}
|
|
if !logger.allowByDedup(entry) {
|
|
return
|
|
}
|
|
if !logger.allowBySampling(entry) {
|
|
return
|
|
}
|
|
if !logger.allowByRateLimit(entry) {
|
|
return
|
|
}
|
|
messageText := snapshot.renderEntryMessage(entry)
|
|
defaultPlain := snapshot.composeLine(timestamp, meta, levelTag, messageText)
|
|
plainLine := snapshot.formatPlainFromEntry(entry, defaultPlain)
|
|
plainLine = appendNewlineIfNeeded(plainLine, snapshot.autoAppendNewline)
|
|
displayLine := plainLine
|
|
logData := LogData{
|
|
Log: plainLine,
|
|
Colors: snapshot.levelAttrs(level),
|
|
Name: snapshot.name,
|
|
}
|
|
|
|
if isShow && snapshot.showColor {
|
|
if snapshot.onlyColorLevel && snapshot.formatter == nil {
|
|
levelSegment := levelTag
|
|
if snapshot.showLevel {
|
|
levelSegment = snapshot.levelColor(level).Sprint(levelTag)
|
|
}
|
|
messageSegment := snapshot.renderDisplayMessage(entry)
|
|
displayLine = snapshot.composeLine(timestamp, meta, levelSegment, messageSegment)
|
|
} else {
|
|
displayLine = snapshot.levelColor(level).Sprint(plainLine)
|
|
}
|
|
}
|
|
|
|
if isShow {
|
|
if level < snapshot.errOutputLevel {
|
|
if snapshot.showColor {
|
|
if _, err := fmt.Fprint(stdScreen, displayLine); err != nil {
|
|
reportWriteError(err, logData)
|
|
}
|
|
} else {
|
|
if _, err := fmt.Fprint(os.Stdout, displayLine); err != nil {
|
|
reportWriteError(err, logData)
|
|
}
|
|
}
|
|
} else {
|
|
if snapshot.showColor {
|
|
if _, err := fmt.Fprint(errScreen, displayLine); err != nil {
|
|
reportWriteError(err, logData)
|
|
}
|
|
} else {
|
|
if _, err := fmt.Fprint(os.Stderr, displayLine); err != nil {
|
|
reportWriteError(err, logData)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if handler != nil {
|
|
logger.enqueueAsyncTransfer(logTransfer{
|
|
handlerFunc: handler,
|
|
LogData: logData,
|
|
}, true)
|
|
}
|
|
if snapshot.entryHandler != nil {
|
|
entryCopy := *entry
|
|
logger.enqueueAsyncTransfer(logTransfer{
|
|
handlerFunc: func(data LogData) {
|
|
entryVal := entryCopy
|
|
logger.invokeEntryHandler(snapshot.entryHandler, snapshot.entryHandlerTimeout, &entryVal, data)
|
|
},
|
|
LogData: logData,
|
|
}, false)
|
|
}
|
|
if !snapshot.stopWriter {
|
|
logger.writeWithData(plainLine, logData)
|
|
}
|
|
}
|
|
|
|
func (logger *starlog) write(logStr string) {
|
|
logger.writeWithData(logStr, LogData{Log: logStr})
|
|
}
|
|
|
|
func (logger *starlog) writeWithData(logStr string, data LogData) {
|
|
logger.mu.Lock()
|
|
defer logger.mu.Unlock()
|
|
|
|
if logger.stopWriter {
|
|
return
|
|
}
|
|
if data.Name == "" {
|
|
data.Name = logger.name
|
|
}
|
|
logStr = appendNewlineIfNeeded(logStr, logger.autoAppendNewline)
|
|
data.Log = logStr
|
|
if logger.switching {
|
|
if logger.enqueuePendingWriteLocked(logStr, data) {
|
|
return
|
|
}
|
|
}
|
|
logger.flushPendingWritesLocked(data.Name, data.Colors)
|
|
if err := logger.writeDirect(logStr); err != nil {
|
|
reportWriteError(err, data)
|
|
}
|
|
}
|
|
|
|
func (logger *starlog) writePendingLocked() {
|
|
if logger.stopWriter {
|
|
logger.pendingWrites = nil
|
|
logger.signalPendingCondLocked()
|
|
return
|
|
}
|
|
logger.flushPendingWritesLocked(logger.name, nil)
|
|
logger.signalPendingCondLocked()
|
|
}
|
|
|
|
func (logger *starlog) print(str ...interface{}) string {
|
|
return fmt.Sprint(str...)
|
|
}
|
|
|
|
func (logger *starlog) printf(format string, str ...interface{}) string {
|
|
return fmt.Sprintf(format, str...)
|
|
}
|
|
|
|
func (logger *starlog) println(str ...interface{}) string {
|
|
return fmt.Sprintln(str...)
|
|
}
|
|
|
|
func (logger *starlog) Debug(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprint(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvDebug, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Debugf(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
|
strs := fmt.Sprintf(format, str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvDebug, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Debugln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprintln(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvDebug, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Info(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprint(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvInfo, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Infof(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
|
strs := fmt.Sprintf(format, str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvInfo, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Infoln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprintln(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvInfo, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Notice(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprint(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvNotice, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Noticef(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
|
strs := fmt.Sprintf(format, str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvNotice, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Noticeln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprintln(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvNotice, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Warning(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprint(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvWarning, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Warningf(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
|
strs := fmt.Sprintf(format, str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvWarning, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Warningln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprintln(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvWarning, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Error(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprint(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvError, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Errorf(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
|
strs := fmt.Sprintf(format, str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvError, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Errorln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprintln(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvError, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Critical(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprint(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvCritical, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Criticalf(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
|
strs := fmt.Sprintf(format, str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvCritical, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Criticalln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprintln(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvCritical, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Fatal(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprint(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvFatal, strs, nil, nil, nil)
|
|
os.Exit(9)
|
|
}
|
|
|
|
func (logger *starlog) Fatalf(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
|
strs := fmt.Sprintf(format, str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvFatal, strs, nil, nil, nil)
|
|
os.Exit(9)
|
|
}
|
|
|
|
func (logger *starlog) Fatalln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprintln(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvFatal, strs, nil, nil, nil)
|
|
os.Exit(9)
|
|
}
|
|
|
|
func (logger *starlog) Panic(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprint(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvPanic, strs, nil, nil, nil)
|
|
panic(str)
|
|
}
|
|
|
|
func (logger *starlog) Panicf(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
|
strs := fmt.Sprintf(format, str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvPanic, strs, nil, nil, nil)
|
|
panic(fmt.Sprintf(format, str...))
|
|
}
|
|
|
|
func (logger *starlog) Panicln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprintln(str...)
|
|
logger.build(thread, isStd, logger.showStd, handler, LvPanic, strs, nil, nil, nil)
|
|
panic(fmt.Sprintln(str...))
|
|
}
|
|
|
|
func (logger *starlog) Print(thread string, isStd bool, isShow bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprint(str...)
|
|
if isShow {
|
|
fmt.Print(strs)
|
|
}
|
|
logger.write(strs)
|
|
}
|
|
|
|
func (logger *starlog) Printf(thread string, isStd bool, isShow bool, handler func(LogData), format string, str ...interface{}) {
|
|
strs := fmt.Sprintf(format, str...)
|
|
if isShow {
|
|
fmt.Print(strs)
|
|
}
|
|
logger.write(strs)
|
|
}
|
|
|
|
func (logger *starlog) Println(thread string, isStd bool, isShow bool, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprintln(str...)
|
|
if isShow {
|
|
fmt.Print(strs)
|
|
}
|
|
logger.write(strs)
|
|
}
|
|
|
|
func (logger *starlog) Log(thread string, isStd bool, isShow bool, level int, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprint(str...)
|
|
logger.build(thread, isStd, isShow, handler, level, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Logf(thread string, isStd bool, isShow bool, level int, handler func(LogData), format string, str ...interface{}) {
|
|
strs := fmt.Sprintf(format, str...)
|
|
logger.build(thread, isStd, isShow, handler, level, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Logln(thread string, isStd bool, isShow bool, level int, handler func(LogData), str ...interface{}) {
|
|
strs := fmt.Sprintln(str...)
|
|
logger.build(thread, isStd, isShow, handler, level, strs, nil, nil, nil)
|
|
}
|
|
|
|
func (logger *starlog) Write(str ...interface{}) {
|
|
strs := fmt.Sprint(str...)
|
|
logger.write(strs)
|
|
}
|
|
|
|
func (logger *starlog) Writef(format string, str ...interface{}) {
|
|
strs := fmt.Sprintf(format, str...)
|
|
logger.Write(strs)
|
|
}
|
|
|
|
func (logger *starlog) Writeln(str ...interface{}) {
|
|
strs := fmt.Sprintln(str...)
|
|
logger.Write(strs)
|
|
}
|