diff options
Diffstat (limited to 'modules/log')
-rw-r--r-- | modules/log/event_format.go | 36 | ||||
-rw-r--r-- | modules/log/event_writer_base.go | 2 | ||||
-rw-r--r-- | modules/log/event_writer_conn_test.go | 3 | ||||
-rw-r--r-- | modules/log/flags.go | 2 | ||||
-rw-r--r-- | modules/log/flags_test.go | 14 | ||||
-rw-r--r-- | modules/log/level_test.go | 6 | ||||
-rw-r--r-- | modules/log/logger.go | 4 | ||||
-rw-r--r-- | modules/log/logger_global.go | 2 | ||||
-rw-r--r-- | modules/log/logger_impl.go | 56 | ||||
-rw-r--r-- | modules/log/logger_test.go | 83 | ||||
-rw-r--r-- | modules/log/manager_test.go | 2 | ||||
-rw-r--r-- | modules/log/misc.go | 16 |
12 files changed, 142 insertions, 84 deletions
diff --git a/modules/log/event_format.go b/modules/log/event_format.go index 8fda0a4980..4cf471d223 100644 --- a/modules/log/event_format.go +++ b/modules/log/event_format.go @@ -125,15 +125,19 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms buf = append(buf, resetBytes...) } } - if flags&(Lshortfile|Llongfile) != 0 { + if flags&(Lshortfile|Llongfile) != 0 && event.Filename != "" { if mode.Colorize { buf = append(buf, fgGreenBytes...) } file := event.Filename if flags&Lmedfile == Lmedfile { - startIndex := len(file) - 20 - if startIndex > 0 { - file = "..." + file[startIndex:] + fileLen := len(file) + const softLimit = 20 + if fileLen > softLimit { + slashIndex := strings.LastIndexByte(file[:fileLen-softLimit], '/') + if slashIndex != -1 { + file = ".../" + file[slashIndex+1:] + } } } else if flags&Lshortfile != 0 { startIndex := strings.LastIndexByte(file, '/') @@ -157,14 +161,22 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms if mode.Colorize { buf = append(buf, fgGreenBytes...) } - funcname := event.Caller + funcName := event.Caller + shortFuncName := funcName if flags&Lshortfuncname != 0 { - lastIndex := strings.LastIndexByte(funcname, '.') - if lastIndex > 0 && len(funcname) > lastIndex+1 { - funcname = funcname[lastIndex+1:] + // funcName = "code.gitea.io/gitea/modules/foo/bar.MyFunc.func1.2()" + slashPos := strings.LastIndexByte(funcName, '/') + dotPos := strings.IndexByte(funcName[slashPos+1:], '.') + if dotPos > 0 { + // shortFuncName = "MyFunc.func1.2()" + shortFuncName = funcName[slashPos+1+dotPos+1:] + if strings.Contains(shortFuncName, ".") { + shortFuncName = strings.ReplaceAll(shortFuncName, ".func", ".") + } } + funcName = shortFuncName } - buf = append(buf, funcname...) + buf = append(buf, funcName...) if mode.Colorize { buf = append(buf, resetBytes...) } @@ -200,7 +212,7 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms } } if hasColorValue { - msg = []byte(fmt.Sprintf(msgFormat, msgArgs...)) + msg = fmt.Appendf(nil, msgFormat, msgArgs...) } } // try to re-use the pre-formatted simple text message @@ -231,8 +243,8 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms buf = append(buf, msg...) if event.Stacktrace != "" && mode.StacktraceLevel <= event.Level { - lines := bytes.Split([]byte(event.Stacktrace), []byte("\n")) - for _, line := range lines { + lines := bytes.SplitSeq([]byte(event.Stacktrace), []byte("\n")) + for line := range lines { buf = append(buf, "\n\t"...) buf = append(buf, line...) } diff --git a/modules/log/event_writer_base.go b/modules/log/event_writer_base.go index c327c48ca2..9189ca4e90 100644 --- a/modules/log/event_writer_base.go +++ b/modules/log/event_writer_base.go @@ -105,7 +105,7 @@ func (b *EventWriterBaseImpl) Run(ctx context.Context) { case io.WriterTo: _, err = msg.WriteTo(b.OutputWriteCloser) default: - _, err = b.OutputWriteCloser.Write([]byte(fmt.Sprint(msg))) + _, err = fmt.Fprint(b.OutputWriteCloser, msg) } if err != nil { FallbackErrorf("unable to write log message of %q (%v): %v", b.Name, err, event.Msg) diff --git a/modules/log/event_writer_conn_test.go b/modules/log/event_writer_conn_test.go index 69e87aa8c4..2aff37812d 100644 --- a/modules/log/event_writer_conn_test.go +++ b/modules/log/event_writer_conn_test.go @@ -4,7 +4,6 @@ package log import ( - "context" "fmt" "io" "net" @@ -40,7 +39,7 @@ func TestConnLogger(t *testing.T) { level := INFO flags := LstdFlags | LUTC | Lfuncname - logger := NewLoggerWithWriters(context.Background(), "test", NewEventWriterConn("test-conn", WriterMode{ + logger := NewLoggerWithWriters(t.Context(), "test", NewEventWriterConn("test-conn", WriterMode{ Level: level, Prefix: prefix, Flags: FlagsFromBits(flags), diff --git a/modules/log/flags.go b/modules/log/flags.go index 8064c91745..f409261150 100644 --- a/modules/log/flags.go +++ b/modules/log/flags.go @@ -123,7 +123,7 @@ func FlagsFromString(from string, def ...uint32) Flags { return Flags{defined: true, flags: def[0]} } flags := uint32(0) - for _, flag := range strings.Split(strings.ToLower(from), ",") { + for flag := range strings.SplitSeq(strings.ToLower(from), ",") { flags |= flagFromString[strings.TrimSpace(flag)] } return Flags{defined: true, flags: flags} diff --git a/modules/log/flags_test.go b/modules/log/flags_test.go index 03972a9fb0..6eb65d8114 100644 --- a/modules/log/flags_test.go +++ b/modules/log/flags_test.go @@ -12,19 +12,19 @@ import ( ) func TestFlags(t *testing.T) { - assert.EqualValues(t, Ldefault, Flags{}.Bits()) + assert.Equal(t, Ldefault, Flags{}.Bits()) assert.EqualValues(t, 0, FlagsFromString("").Bits()) - assert.EqualValues(t, Lgopid, FlagsFromString("", Lgopid).Bits()) + assert.Equal(t, Lgopid, FlagsFromString("", Lgopid).Bits()) assert.EqualValues(t, 0, FlagsFromString("none", Lgopid).Bits()) - assert.EqualValues(t, Ldate|Ltime, FlagsFromString("date,time", Lgopid).Bits()) + assert.Equal(t, Ldate|Ltime, FlagsFromString("date,time", Lgopid).Bits()) - assert.EqualValues(t, "stdflags", FlagsFromString("stdflags").String()) - assert.EqualValues(t, "medfile", FlagsFromString("medfile").String()) + assert.Equal(t, "stdflags", FlagsFromString("stdflags").String()) + assert.Equal(t, "medfile", FlagsFromString("medfile").String()) bs, err := json.Marshal(FlagsFromString("utc,level")) assert.NoError(t, err) - assert.EqualValues(t, `"level,utc"`, string(bs)) + assert.Equal(t, `"level,utc"`, string(bs)) var flags Flags assert.NoError(t, json.Unmarshal(bs, &flags)) - assert.EqualValues(t, LUTC|Llevel, flags.Bits()) + assert.Equal(t, LUTC|Llevel, flags.Bits()) } diff --git a/modules/log/level_test.go b/modules/log/level_test.go index cd18a807d8..0e59af6cb7 100644 --- a/modules/log/level_test.go +++ b/modules/log/level_test.go @@ -32,11 +32,11 @@ func TestLevelMarshalUnmarshalJSON(t *testing.T) { assert.NoError(t, err) assert.Equal(t, INFO, testLevel.Level) - err = json.Unmarshal([]byte(fmt.Sprintf(`{"level":%d}`, 2)), &testLevel) + err = json.Unmarshal(fmt.Appendf(nil, `{"level":%d}`, 2), &testLevel) assert.NoError(t, err) assert.Equal(t, INFO, testLevel.Level) - err = json.Unmarshal([]byte(fmt.Sprintf(`{"level":%d}`, 10012)), &testLevel) + err = json.Unmarshal(fmt.Appendf(nil, `{"level":%d}`, 10012), &testLevel) assert.NoError(t, err) assert.Equal(t, INFO, testLevel.Level) @@ -51,5 +51,5 @@ func TestLevelMarshalUnmarshalJSON(t *testing.T) { } func makeTestLevelBytes(level string) []byte { - return []byte(fmt.Sprintf(`{"level":"%s"}`, level)) + return fmt.Appendf(nil, `{"level":"%s"}`, level) } diff --git a/modules/log/logger.go b/modules/log/logger.go index a833b6ef0f..8b89e0eb5a 100644 --- a/modules/log/logger.go +++ b/modules/log/logger.go @@ -24,7 +24,7 @@ package log // BaseLogger provides the basic logging functions type BaseLogger interface { - Log(skip int, level Level, format string, v ...any) + Log(skip int, event *Event, format string, v ...any) GetLevel() Level } @@ -45,6 +45,6 @@ type Logger interface { LevelLogger } -type LogStringer interface { //nolint:revive +type LogStringer interface { //nolint:revive // export stutter LogString() string } diff --git a/modules/log/logger_global.go b/modules/log/logger_global.go index 6ce8b70fed..07c25cd62f 100644 --- a/modules/log/logger_global.go +++ b/modules/log/logger_global.go @@ -18,7 +18,7 @@ func GetLevel() Level { } func Log(skip int, level Level, format string, v ...any) { - GetLogger(DEFAULT).Log(skip+1, level, format, v...) + GetLogger(DEFAULT).Log(skip+1, &Event{Level: level}, format, v...) } func Trace(format string, v ...any) { diff --git a/modules/log/logger_impl.go b/modules/log/logger_impl.go index 76dd5f43fb..551c1454aa 100644 --- a/modules/log/logger_impl.go +++ b/modules/log/logger_impl.go @@ -5,6 +5,7 @@ package log import ( "context" + "reflect" "runtime" "strings" "sync" @@ -175,29 +176,42 @@ func (l *LoggerImpl) IsEnabled() bool { return l.level.Load() < int32(FATAL) && len(l.eventWriters) > 0 } +func asLogStringer(v any) LogStringer { + if s, ok := v.(LogStringer); ok { + return s + } else if a := reflect.ValueOf(v); a.Kind() == reflect.Struct { + // in case the receiver is a pointer, but the value is a struct + vp := reflect.New(a.Type()) + vp.Elem().Set(a) + if s, ok := vp.Interface().(LogStringer); ok { + return s + } + } + return nil +} + // Log prepares the log event, if the level matches, the event will be sent to the writers -func (l *LoggerImpl) Log(skip int, level Level, format string, logArgs ...any) { - if Level(l.level.Load()) > level { +func (l *LoggerImpl) Log(skip int, event *Event, format string, logArgs ...any) { + if Level(l.level.Load()) > event.Level { return } - event := &Event{ - Time: time.Now(), - Level: level, - Caller: "?()", + if event.Time.IsZero() { + event.Time = time.Now() } - - pc, filename, line, ok := runtime.Caller(skip + 1) - if ok { - fn := runtime.FuncForPC(pc) - if fn != nil { - event.Caller = fn.Name() + "()" + if event.Caller == "" { + pc, filename, line, ok := runtime.Caller(skip + 1) + if ok { + fn := runtime.FuncForPC(pc) + if fn != nil { + fnName := fn.Name() + event.Caller = strings.ReplaceAll(fnName, "[...]", "") + "()" // generic function names are "foo[...]" + } + } + event.Filename, event.Line = strings.TrimPrefix(filename, projectPackagePrefix), line + if l.stacktraceLevel.Load() <= int32(event.Level) { + event.Stacktrace = Stack(skip + 1) } - } - event.Filename, event.Line = strings.TrimPrefix(filename, projectPackagePrefix), line - - if l.stacktraceLevel.Load() <= int32(level) { - event.Stacktrace = Stack(skip + 1) } // get a simple text message without color @@ -207,11 +221,11 @@ func (l *LoggerImpl) Log(skip int, level Level, format string, logArgs ...any) { // handle LogStringer values for i, v := range msgArgs { if cv, ok := v.(*ColoredValue); ok { - if s, ok := cv.v.(LogStringer); ok { - cv.v = logStringFormatter{v: s} + if ls := asLogStringer(cv.v); ls != nil { + cv.v = logStringFormatter{v: ls} } - } else if s, ok := v.(LogStringer); ok { - msgArgs[i] = logStringFormatter{v: s} + } else if ls := asLogStringer(v); ls != nil { + msgArgs[i] = logStringFormatter{v: ls} } } diff --git a/modules/log/logger_test.go b/modules/log/logger_test.go index 0de14eb411..a74139dc51 100644 --- a/modules/log/logger_test.go +++ b/modules/log/logger_test.go @@ -4,7 +4,7 @@ package log import ( - "context" + "regexp" "sync" "testing" "time" @@ -35,11 +35,11 @@ func (d *dummyWriter) Close() error { return nil } -func (d *dummyWriter) GetLogs() []string { +func (d *dummyWriter) FetchLogs() []string { d.mu.Lock() defer d.mu.Unlock() - logs := make([]string, len(d.logs)) - copy(logs, d.logs) + logs := d.logs + d.logs = nil return logs } @@ -53,20 +53,20 @@ func newDummyWriter(name string, level Level, delay time.Duration) *dummyWriter } func TestLogger(t *testing.T) { - logger := NewLoggerWithWriters(context.Background(), "test") + logger := NewLoggerWithWriters(t.Context(), "test") dump := logger.DumpWriters() assert.Empty(t, dump) - assert.EqualValues(t, NONE, logger.GetLevel()) + assert.Equal(t, NONE, logger.GetLevel()) assert.False(t, logger.IsEnabled()) w1 := newDummyWriter("dummy-1", DEBUG, 0) logger.AddWriters(w1) - assert.EqualValues(t, DEBUG, logger.GetLevel()) + assert.Equal(t, DEBUG, logger.GetLevel()) w2 := newDummyWriter("dummy-2", WARN, 200*time.Millisecond) logger.AddWriters(w2) - assert.EqualValues(t, DEBUG, logger.GetLevel()) + assert.Equal(t, DEBUG, logger.GetLevel()) dump = logger.DumpWriters() assert.Len(t, dump, 2) @@ -77,18 +77,18 @@ func TestLogger(t *testing.T) { // w2 is slow, so only w1 has logs time.Sleep(100 * time.Millisecond) - assert.Equal(t, []string{"debug-level\n", "error-level\n"}, w1.GetLogs()) - assert.Equal(t, []string{}, w2.GetLogs()) + assert.Equal(t, []string{"debug-level\n", "error-level\n"}, w1.FetchLogs()) + assert.Empty(t, w2.FetchLogs()) logger.Close() // after Close, all logs are flushed - assert.Equal(t, []string{"debug-level\n", "error-level\n"}, w1.GetLogs()) - assert.Equal(t, []string{"error-level\n"}, w2.GetLogs()) + assert.Empty(t, w1.FetchLogs()) + assert.Equal(t, []string{"error-level\n"}, w2.FetchLogs()) } func TestLoggerPause(t *testing.T) { - logger := NewLoggerWithWriters(context.Background(), "test") + logger := NewLoggerWithWriters(t.Context(), "test") w1 := newDummyWriter("dummy-1", DEBUG, 0) logger.AddWriters(w1) @@ -98,12 +98,12 @@ func TestLoggerPause(t *testing.T) { logger.Info("info-level") time.Sleep(100 * time.Millisecond) - assert.Equal(t, []string{}, w1.GetLogs()) + assert.Empty(t, w1.FetchLogs()) GetManager().ResumeAll() time.Sleep(100 * time.Millisecond) - assert.Equal(t, []string{"info-level\n"}, w1.GetLogs()) + assert.Equal(t, []string{"info-level\n"}, w1.FetchLogs()) logger.Close() } @@ -116,21 +116,54 @@ func (t testLogString) LogString() string { return "log-string" } -func TestLoggerLogString(t *testing.T) { - logger := NewLoggerWithWriters(context.Background(), "test") +type testLogStringPtrReceiver struct { + Field string +} - w1 := newDummyWriter("dummy-1", DEBUG, 0) - w1.Mode.Colorize = true - logger.AddWriters(w1) +func (t *testLogStringPtrReceiver) LogString() string { + return "log-string-ptr-receiver" +} - logger.Info("%s %s %#v %v", testLogString{}, &testLogString{}, testLogString{Field: "detail"}, NewColoredValue(testLogString{}, FgRed)) - logger.Close() +func genericFunc[T any](logger Logger, v T) { + logger.Info("from genericFunc: %v", v) +} - assert.Equal(t, []string{"log-string log-string log.testLogString{Field:\"detail\"} \x1b[31mlog-string\x1b[0m\n"}, w1.GetLogs()) +func TestLoggerOutput(t *testing.T) { + t.Run("LogString", func(t *testing.T) { + logger := NewLoggerWithWriters(t.Context(), "test") + w1 := newDummyWriter("dummy-1", DEBUG, 0) + w1.Mode.Colorize = true + logger.AddWriters(w1) + logger.Info("%s %s %#v %v", testLogString{}, &testLogString{}, testLogString{Field: "detail"}, NewColoredValue(testLogString{}, FgRed)) + logger.Info("%s %s %#v %v", testLogStringPtrReceiver{}, &testLogStringPtrReceiver{}, testLogStringPtrReceiver{Field: "detail"}, NewColoredValue(testLogStringPtrReceiver{}, FgRed)) + logger.Close() + + assert.Equal(t, []string{ + "log-string log-string log.testLogString{Field:\"detail\"} \x1b[31mlog-string\x1b[0m\n", + "log-string-ptr-receiver log-string-ptr-receiver &log.testLogStringPtrReceiver{Field:\"detail\"} \x1b[31mlog-string-ptr-receiver\x1b[0m\n", + }, w1.FetchLogs()) + }) + + t.Run("Caller", func(t *testing.T) { + logger := NewLoggerWithWriters(t.Context(), "test") + w1 := newDummyWriter("dummy-1", DEBUG, 0) + w1.EventWriterBaseImpl.Mode.Flags.flags = Lmedfile | Lshortfuncname + logger.AddWriters(w1) + anonymousFunc := func(logger Logger) { + logger.Info("from anonymousFunc") + } + genericFunc(logger, "123") + anonymousFunc(logger) + logger.Close() + logs := w1.FetchLogs() + assert.Len(t, logs, 2) + assert.Regexp(t, `modules/log/logger_test.go:\w+:`+regexp.QuoteMeta(`genericFunc() from genericFunc: 123`), logs[0]) + assert.Regexp(t, `modules/log/logger_test.go:\w+:`+regexp.QuoteMeta(`TestLoggerOutput.2.1() from anonymousFunc`), logs[1]) + }) } func TestLoggerExpressionFilter(t *testing.T) { - logger := NewLoggerWithWriters(context.Background(), "test") + logger := NewLoggerWithWriters(t.Context(), "test") w1 := newDummyWriter("dummy-1", DEBUG, 0) w1.Mode.Expression = "foo.*" @@ -142,5 +175,5 @@ func TestLoggerExpressionFilter(t *testing.T) { logger.SendLogEvent(&Event{Level: INFO, Filename: "foo.go", MsgSimpleText: "by filename"}) logger.Close() - assert.Equal(t, []string{"foo\n", "foo bar\n", "by filename\n"}, w1.GetLogs()) + assert.Equal(t, []string{"foo\n", "foo bar\n", "by filename\n"}, w1.FetchLogs()) } diff --git a/modules/log/manager_test.go b/modules/log/manager_test.go index b8fbf84613..beddbccb73 100644 --- a/modules/log/manager_test.go +++ b/modules/log/manager_test.go @@ -37,6 +37,6 @@ func TestSharedWorker(t *testing.T) { m.Close() - logs := w.(*dummyWriter).GetLogs() + logs := w.(*dummyWriter).FetchLogs() assert.Equal(t, []string{"msg-1\n", "msg-2\n", "msg-3\n"}, logs) } diff --git a/modules/log/misc.go b/modules/log/misc.go index ae4ce04cf3..c9d230e4ac 100644 --- a/modules/log/misc.go +++ b/modules/log/misc.go @@ -19,8 +19,8 @@ func BaseLoggerToGeneralLogger(b BaseLogger) Logger { var _ Logger = (*baseToLogger)(nil) -func (s *baseToLogger) Log(skip int, level Level, format string, v ...any) { - s.base.Log(skip+1, level, format, v...) +func (s *baseToLogger) Log(skip int, event *Event, format string, v ...any) { + s.base.Log(skip+1, event, format, v...) } func (s *baseToLogger) GetLevel() Level { @@ -32,27 +32,27 @@ func (s *baseToLogger) LevelEnabled(level Level) bool { } func (s *baseToLogger) Trace(format string, v ...any) { - s.base.Log(1, TRACE, format, v...) + s.base.Log(1, &Event{Level: TRACE}, format, v...) } func (s *baseToLogger) Debug(format string, v ...any) { - s.base.Log(1, DEBUG, format, v...) + s.base.Log(1, &Event{Level: DEBUG}, format, v...) } func (s *baseToLogger) Info(format string, v ...any) { - s.base.Log(1, INFO, format, v...) + s.base.Log(1, &Event{Level: INFO}, format, v...) } func (s *baseToLogger) Warn(format string, v ...any) { - s.base.Log(1, WARN, format, v...) + s.base.Log(1, &Event{Level: WARN}, format, v...) } func (s *baseToLogger) Error(format string, v ...any) { - s.base.Log(1, ERROR, format, v...) + s.base.Log(1, &Event{Level: ERROR}, format, v...) } func (s *baseToLogger) Critical(format string, v ...any) { - s.base.Log(1, CRITICAL, format, v...) + s.base.Log(1, &Event{Level: CRITICAL}, format, v...) } type PrintfLogger struct { |