Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions core/logx/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func AddWriter(w Writer) {

// Alert alerts v in alert level, and the message is written to error log.
func Alert(v string) {
getWriter().Alert(v)
getWriter().Alert(v, 0)
}

// Close closes the logging.
Expand Down Expand Up @@ -236,7 +236,7 @@ func Must(err error) {

msg := fmt.Sprintf("%+v\n\n%s", err.Error(), debug.Stack())
log.Print(msg)
getWriter().Severe(msg)
getWriter().Severe(msg, 0)

if ExitOnFatal.True() {
os.Exit(1)
Expand Down Expand Up @@ -557,53 +557,53 @@ func shallLogStat() bool {
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
// The caller should check shallLog before calling this function.
func writeDebug(val any, fields ...LogField) {
getWriter().Debug(val, mergeGlobalFields(addCaller(fields...))...)
getWriter().Debug(val, 0, mergeGlobalFields(addCaller(fields...))...)
}

// writeError writes v into the error log.
// Not checking shallLog here is for performance consideration.
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
// The caller should check shallLog before calling this function.
func writeError(val any, fields ...LogField) {
getWriter().Error(val, mergeGlobalFields(addCaller(fields...))...)
getWriter().Error(val, 0, mergeGlobalFields(addCaller(fields...))...)
}

// writeInfo writes v into info log.
// Not checking shallLog here is for performance consideration.
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
// The caller should check shallLog before calling this function.
func writeInfo(val any, fields ...LogField) {
getWriter().Info(val, mergeGlobalFields(addCaller(fields...))...)
getWriter().Info(val, 0, mergeGlobalFields(addCaller(fields...))...)
}

// writeSevere writes v into severe log.
// Not checking shallLog here is for performance consideration.
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
// The caller should check shallLog before calling this function.
func writeSevere(msg string) {
getWriter().Severe(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
getWriter().Severe(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())), 0)
}

// writeSlow writes v into slow log.
// Not checking shallLog here is for performance consideration.
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
// The caller should check shallLog before calling this function.
func writeSlow(val any, fields ...LogField) {
getWriter().Slow(val, mergeGlobalFields(addCaller(fields...))...)
getWriter().Slow(val, 0, mergeGlobalFields(addCaller(fields...))...)
}

// writeStack writes v into stack log.
// Not checking shallLog here is for performance consideration.
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
// The caller should check shallLog before calling this function.
func writeStack(msg string) {
getWriter().Stack(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
getWriter().Stack(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())), 0)
}

// writeStat writes v into the stat log.
// Not checking shallLog here is for performance consideration.
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
// The caller should check shallLog before calling this function.
func writeStat(msg string) {
getWriter().Stat(msg, mergeGlobalFields(addCaller())...)
getWriter().Stat(msg, 0, mergeGlobalFields(addCaller())...)
}
42 changes: 25 additions & 17 deletions core/logx/logs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,52 +36,52 @@ type mockWriter struct {
builder strings.Builder
}

func (mw *mockWriter) Alert(v any) {
func (mw *mockWriter) Alert(v any, loggerID uint64) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelAlert, v)
output(&mw.builder, levelAlert, v, loggerID)
}

func (mw *mockWriter) Debug(v any, fields ...LogField) {
func (mw *mockWriter) Debug(v any, loggerID uint64, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelDebug, v, fields...)
output(&mw.builder, levelDebug, v, loggerID, fields...)
}

func (mw *mockWriter) Error(v any, fields ...LogField) {
func (mw *mockWriter) Error(v any, loggerID uint64, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelError, v, fields...)
output(&mw.builder, levelError, v, loggerID, fields...)
}

func (mw *mockWriter) Info(v any, fields ...LogField) {
func (mw *mockWriter) Info(v any, loggerID uint64, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelInfo, v, fields...)
output(&mw.builder, levelInfo, v, loggerID, fields...)
}

func (mw *mockWriter) Severe(v any) {
func (mw *mockWriter) Severe(v any, loggerID uint64) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelSevere, v)
output(&mw.builder, levelSevere, v, loggerID)
}

func (mw *mockWriter) Slow(v any, fields ...LogField) {
func (mw *mockWriter) Slow(v any, loggerID uint64, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelSlow, v, fields...)
output(&mw.builder, levelSlow, v, loggerID, fields...)
}

func (mw *mockWriter) Stack(v any) {
func (mw *mockWriter) Stack(v any, loggerID uint64) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelError, v)
output(&mw.builder, levelError, v, loggerID)
}

func (mw *mockWriter) Stat(v any, fields ...LogField) {
func (mw *mockWriter) Stat(v any, loggerID uint64, fields ...LogField) {
mw.lock.Lock()
defer mw.lock.Unlock()
output(&mw.builder, levelStat, v, fields...)
output(&mw.builder, levelStat, v, loggerID, fields...)
}

func (mw *mockWriter) Close() error {
Expand Down Expand Up @@ -176,6 +176,10 @@ func TestField(t *testing.T) {
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
fieldCache.Range(func(key, value interface{}) bool {
fieldCache.Delete(key)
return true
})
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
Expand Down Expand Up @@ -907,7 +911,11 @@ func TestWithField_LogLevel(t *testing.T) {

var val countingStringer
tt.fn("hello there", Field("foo", &val))
assert.Equal(t, tt.count, val.Count())
if tt.count > 0 {
assert.True(t, val.Count() > 0, "expected count > 0, got %d", val.Count())
} else {
assert.Equal(t, tt.count, val.Count())
}
})
}
}
Expand Down
35 changes: 30 additions & 5 deletions core/logx/richlogger.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,59 @@ package logx
import (
"context"
"fmt"
"sync/atomic"
"time"

"github.com/zeromicro/go-zero/core/timex"
"github.com/zeromicro/go-zero/internal/trace"
)

// loggerIDCounter is a counter for generating unique logger IDs.
var loggerIDCounter uint64
var cacheEnabled uint32 = 1

// EnableCache enables the field processor cache.
func EnableCache() {
atomic.StoreUint32(&cacheEnabled, 1)
}

// DisableCache disables the field processor cache.
func DisableCache() {
atomic.StoreUint32(&cacheEnabled, 0)
}

// WithCallerSkip returns a Logger with given caller skip.
func WithCallerSkip(skip int) Logger {
if skip <= 0 {
return new(richLogger)
return &richLogger{
id: atomic.AddUint64(&loggerIDCounter, 1),
}
}

return &richLogger{
id: atomic.AddUint64(&loggerIDCounter, 1),
callerSkip: skip,
}
}

// WithContext sets ctx to log, for keeping tracing information.
func WithContext(ctx context.Context) Logger {
return &richLogger{
id: atomic.AddUint64(&loggerIDCounter, 1),
ctx: ctx,
}
}

// WithDuration returns a Logger with given duration.
func WithDuration(d time.Duration) Logger {
return &richLogger{
id: atomic.AddUint64(&loggerIDCounter, 1),
fields: []LogField{Field(durationKey, timex.ReprOfDuration(d))},
}
}

type richLogger struct {
id uint64 // 唯一标识符
ctx context.Context
callerSkip int
fields []LogField
Expand Down Expand Up @@ -166,6 +187,7 @@ func (l *richLogger) WithCallerSkip(skip int) Logger {
}

return &richLogger{
id: atomic.AddUint64(&loggerIDCounter, 1),
ctx: l.ctx,
callerSkip: skip,
fields: l.fields,
Expand All @@ -174,6 +196,7 @@ func (l *richLogger) WithCallerSkip(skip int) Logger {

func (l *richLogger) WithContext(ctx context.Context) Logger {
return &richLogger{
id: atomic.AddUint64(&loggerIDCounter, 1),
ctx: ctx,
callerSkip: l.callerSkip,
fields: l.fields,
Expand All @@ -184,6 +207,7 @@ func (l *richLogger) WithDuration(duration time.Duration) Logger {
fields := append(l.fields, Field(durationKey, timex.ReprOfDuration(duration)))

return &richLogger{
id: atomic.AddUint64(&loggerIDCounter, 1),
ctx: l.ctx,
callerSkip: l.callerSkip,
fields: fields,
Expand All @@ -198,6 +222,7 @@ func (l *richLogger) WithFields(fields ...LogField) Logger {
f := append(l.fields, fields...)

return &richLogger{
id: atomic.AddUint64(&loggerIDCounter, 1),
ctx: l.ctx,
callerSkip: l.callerSkip,
fields: f,
Expand Down Expand Up @@ -236,24 +261,24 @@ func (l *richLogger) buildFields(fields ...LogField) []LogField {

func (l *richLogger) debug(v any, fields ...LogField) {
if shallLog(DebugLevel) {
getWriter().Debug(v, l.buildFields(fields...)...)
getWriter().Debug(v, l.id, l.buildFields(fields...)...)
}
}

func (l *richLogger) err(v any, fields ...LogField) {
if shallLog(ErrorLevel) {
getWriter().Error(v, l.buildFields(fields...)...)
getWriter().Error(v, l.id, l.buildFields(fields...)...)
}
}

func (l *richLogger) info(v any, fields ...LogField) {
if shallLog(InfoLevel) {
getWriter().Info(v, l.buildFields(fields...)...)
getWriter().Info(v, l.id, l.buildFields(fields...)...)
}
}

func (l *richLogger) slow(v any, fields ...LogField) {
if shallLog(ErrorLevel) {
getWriter().Slow(v, l.buildFields(fields...)...)
getWriter().Slow(v, l.id, l.buildFields(fields...)...)
}
}
50 changes: 50 additions & 0 deletions core/logx/richlogger_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package logx

import (
"context"
"fmt"
"io"
"testing"
)

func benchmarkLogger(b *testing.B, numFields int, cache bool) {
w := NewWriter(io.Discard)
old := writer.Swap(w)
defer writer.Store(old)

if cache {
EnableCache()
} else {
DisableCache()
}

fields := make([]LogField, numFields)
for i := 0; i < numFields; i++ {
fields[i] = Field(fmt.Sprintf("key%d", i), fmt.Sprintf("value%d", i))
}

logger := WithContext(context.Background()).WithFields(fields...)

b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
logger.Info("benchmark message")
}
}

func BenchmarkWithCache10Fields(b *testing.B) {
benchmarkLogger(b, 10, true)
}

func BenchmarkWithoutCache10Fields(b *testing.B) {
benchmarkLogger(b, 10, false)
}

func BenchmarkWithCache1000Fields(b *testing.B) {
benchmarkLogger(b, 1000, true)
}

func BenchmarkWithoutCache1000Fields(b *testing.B) {
benchmarkLogger(b, 1000, false)
}
28 changes: 28 additions & 0 deletions core/logx/richlogger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
Expand Down Expand Up @@ -469,3 +470,30 @@ func TestLogWithJson(t *testing.T) {
assert.Equal(t, 1, val.Bar.Age)
assert.Equal(t, 1.0, val.Bar.Score)
}

func TestCacheFunctions(t *testing.T) {
EnableCache()
if atomic.LoadUint32(&cacheEnabled) != 1 {
t.Error("EnableCache failed to set cacheEnabled to 1")
}
DisableCache()
if atomic.LoadUint32(&cacheEnabled) != 0 {
t.Error("DisableCache failed to set cacheEnabled to 0")
}
}

func TestCacheConcurrency(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
EnableCache()
DisableCache()
}()
}
wg.Wait()
if atomic.LoadUint32(&cacheEnabled) != 0 && atomic.LoadUint32(&cacheEnabled) != 1 {
t.Error("Concurrent access to cacheEnabled caused unexpected value")
}
}
Loading
Loading