You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

270 lines
5.9 KiB

package logx
import (
"bytes"
"encoding/json"
"fmt"
"github.com/fatih/color"
"io"
"log"
"sync"
"sync/atomic"
)
type (
Writer interface {
Alert(v any)
Close() error
Debug(v any, fields ...LogField)
Error(v any, fields ...LogField)
Info(v any, fields ...LogField)
Severe(v any)
Slow(v any, fields ...LogField)
Stack(v any)
Stat(v any, fields ...LogField)
}
atomicWriter struct {
writer Writer
lock sync.RWMutex
}
concreteWriter struct {
infoLog io.WriteCloser
errorLog io.WriteCloser
severeLog io.WriteCloser
slowLog io.WriteCloser
statLog io.WriteCloser
stackLog io.Writer
}
)
func NewWriter(w io.Writer) Writer {
lw := newLogWriter(log.New(w, "", flags))
return &concreteWriter{
infoLog: lw,
errorLog: lw,
severeLog: lw,
slowLog: lw,
statLog: lw,
stackLog: lw,
}
}
func (w *atomicWriter) Load() Writer {
w.lock.RLock()
defer w.lock.RUnlock()
return w.writer
}
func (w *atomicWriter) Store(v Writer) {
w.lock.Lock()
defer w.lock.Unlock()
w.writer = v
}
func (w *atomicWriter) StoreIfNil(v Writer) Writer {
w.lock.Lock()
defer w.lock.Unlock()
if w.writer == nil {
w.writer = v
}
return w.writer
}
func (w *atomicWriter) Swap(v Writer) Writer {
w.lock.Lock()
defer w.lock.Unlock()
old := w.writer
w.writer = v
return old
}
func newConsoleWriter() Writer {
outLog := newLogWriter(log.New(color.Output, "", flags))
errLog := newLogWriter(log.New(color.Error, "", flags))
return &concreteWriter{
infoLog: outLog,
errorLog: errLog,
severeLog: errLog,
slowLog: errLog,
statLog: outLog,
stackLog: newLessWriter(errLog, options.logStackCooldownMills),
}
}
func (w *concreteWriter) Alert(v any) {
output(w.errorLog, levelAlert, v)
}
func (w *concreteWriter) Close() error {
if err := w.infoLog.Close(); err != nil {
return err
}
if err := w.errorLog.Close(); err != nil {
return err
}
if err := w.severeLog.Close(); err != nil {
return err
}
if err := w.slowLog.Close(); err != nil {
return err
}
return w.statLog.Close()
}
func (w *concreteWriter) Debug(v any, fields ...LogField) {
output(w.infoLog, levelDebug, v, fields...)
}
func (w *concreteWriter) Error(v any, fields ...LogField) {
output(w.errorLog, levelError, v, fields...)
}
func (w *concreteWriter) Info(v any, fields ...LogField) {
output(w.infoLog, levelInfo, v, fields...)
}
func (w *concreteWriter) Severe(v any) {
output(w.severeLog, levelSevere, v)
}
func (w *concreteWriter) Slow(v any, fields ...LogField) {
output(w.slowLog, levelSlow, v, fields...)
}
func (w *concreteWriter) Stack(v any) {
output(w.stackLog, levelError, v)
}
func (w *concreteWriter) Stat(v any, fields ...LogField) {
output(w.statLog, levelStat, v, fields...)
}
func buildPlainFields(fields ...LogField) []string {
var items []string
for _, field := range fields {
items = append(items, fmt.Sprintf("%s=%+v", field.Key, field.Value))
}
return items
}
func combineGlobalFields(fields []LogField) []LogField {
globals := globalFields.Load()
if globals == nil {
return fields
}
gf := globals.([]LogField)
ret := make([]LogField, 0, len(gf)+len(fields))
ret = append(ret, gf...)
ret = append(ret, fields...)
return ret
}
func output(writer io.Writer, level string, val any, fields ...LogField) {
if v, ok := val.(string); ok {
maxLen := atomic.LoadUint32(&maxContentLength)
if maxLen > 0 && len(v) > int(maxLen) {
val = v[:maxLen]
fields = append(fields, truncatedField)
}
}
fields = combineGlobalFields(fields)
switch atomic.LoadUint32(&encoding) {
case plainEncodingType:
writePlainAny(writer, level, val, buildPlainFields(fields...)...)
default:
entry := make(logEntry)
for _, field := range fields {
entry[field.Key] = field.Value
}
entry[timestampKey] = getTimestamp()
entry[levelKey] = level
entry[contentKey] = val
writeJson(writer, entry)
}
}
func writeJson(writer io.Writer, info any) {
if content, err := json.Marshal(info); err != nil {
log.Println(err.Error())
} else if writer == nil {
log.Println(string(content))
} else {
writer.Write(append(content, '\n'))
}
}
func wrapLevelWithColor(level string) string {
var colour Color
switch level {
case levelAlert:
colour = FgRed
case levelError:
colour = FgRed
case levelFatal:
colour = FgRed
case levelInfo:
colour = FgBlue
case levelSlow:
colour = FgYellow
case levelDebug:
colour = FgYellow
case levelStat:
colour = FgGreen
}
if colour == NoColor {
return level
}
return WithColorPadding(level, colour)
}
func writePlainAny(writer io.Writer, level string, val any, fields ...string) {
level = wrapLevelWithColor(level)
switch v := val.(type) {
case string:
writePlainText(writer, level, v, fields...)
case error:
writePlainText(writer, level, v.Error(), fields...)
case fmt.Stringer:
writePlainText(writer, level, v.String(), fields...)
default:
writePlainValue(writer, level, v, fields...)
}
}
func writePlainText(writer io.Writer, level string, msg string, fields ...string) {
var buf bytes.Buffer
buf.WriteString(getTimestamp())
buf.WriteByte(plainEncodingSep)
buf.WriteString(level)
buf.WriteByte(plainEncodingSep)
buf.WriteString(msg)
for _, item := range fields {
buf.WriteByte(plainEncodingSep)
buf.WriteString(item)
}
buf.WriteByte('\n')
if writer == nil {
log.Println(buf.String())
return
}
if _, err := writer.Write(buf.Bytes()); err != nil {
log.Println(err.Error())
}
}
func writePlainValue(writer io.Writer, level string, val any, fields ...string) {
var buf bytes.Buffer
buf.WriteString(getTimestamp())
buf.WriteByte(plainEncodingSep)
buf.WriteString(level)
buf.WriteByte(plainEncodingSep)
if err := json.NewEncoder(&buf).Encode(val); err != nil {
log.Println(err.Error())
return
}
for _, item := range fields {
buf.WriteByte(plainEncodingSep)
buf.WriteString(item)
}
buf.WriteByte('\n')
if writer == nil {
log.Println(buf.String())
return
}
if _, err := writer.Write(buf.Bytes()); err != nil {
log.Println(err.Error())
}
}