From acaa46b73f40238dcc72f901bea1c6a48c0f7569 Mon Sep 17 00:00:00 2001 From: andrey Date: Wed, 3 Jan 2024 18:44:40 +0300 Subject: [PATCH] update source (#14) Reviewed-on: https://gitoa.ru/go-4devs/log/pulls/14 Co-authored-by: andrey Co-committed-by: andrey --- .golangci.yml | 3 ++ entry/entry.go | 21 +++++++++++ example/log.go | 33 ++++++++++++++++ global.go | 4 +- global_example_test.go | 7 ++-- logger_example_caller_test.go | 15 ++++---- logger_example_test.go | 40 +++++++++++--------- logger_test.go | 6 +-- middleware.go | 6 +++ source.go | 71 +++++++++++++++++++++++++++++------ source_example_test.go | 9 +++-- writter.go | 2 + 12 files changed, 170 insertions(+), 47 deletions(-) create mode 100644 example/log.go diff --git a/.golangci.yml b/.golangci.yml index 7216484..394728e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -70,3 +70,6 @@ issues: linters: - lll - goerr113 + - path: example/* + linters: + - gomnd diff --git a/entry/entry.go b/entry/entry.go index edc9ff0..fde6b9b 100644 --- a/entry/entry.go +++ b/entry/entry.go @@ -161,3 +161,24 @@ func (e *Entry) AddAny(key string, value interface{}) *Entry { func (e *Entry) AddString(key, value string) *Entry { return e.Add(field.String(key, value)) } + +func (e *Entry) Replace(key string, value field.Value) *Entry { + has := false + + e.fields.Fields(func(f field.Field) bool { + if f.Key == key { + f.Value = value + has = true + + return false + } + + return true + }) + + if !has { + e.AddAny(key, value) + } + + return e +} diff --git a/example/log.go b/example/log.go new file mode 100644 index 0000000..f313ca8 --- /dev/null +++ b/example/log.go @@ -0,0 +1,33 @@ +package main + +import ( + "context" + + "gitoa.ru/go-4devs/log" + "gitoa.ru/go-4devs/log/field" +) + +func main() { + ctx := context.Background() + + log.DebugKV(ctx, "debug message") + log.ErrKV(ctx, "error message") + log.Errf(ctx, "format error message:%v", 42) + log.Err(ctx, "error message", 42) + service(ctx, log.Log()) + + logger := log.New(log.WithJSONFormat()).With(log.WithSource(10, log.TrimPath)) + logger.AlertKV(ctx, "alert message new logger", field.String("string", "value")) + service(ctx, logger) +} + +func service(ctx context.Context, logger log.Logger) { + logger = logger.With(log.WithName("service")) + logger.WarnKV(ctx, "warn service message") + otherService(ctx, logger) +} + +func otherService(ctx context.Context, logger log.Logger) { + logger = logger.With(log.WithName("other_service")) + logger.WarnKV(ctx, "warn other service message") +} diff --git a/global.go b/global.go index 389f7c2..7114b45 100644 --- a/global.go +++ b/global.go @@ -8,9 +8,9 @@ import ( "gitoa.ru/go-4devs/log/level" ) -//nolint:gochecknoglobals +//nolint:gochecknoglobals,gomnd var global = With(New(), - WithSource(2), + WithSource(2, TrimPath), WithLevel(KeyLevel, level.Debug), WithExit(level.Alert), WithPanic(level.Emergency), diff --git a/global_example_test.go b/global_example_test.go index 882f70b..79ee466 100644 --- a/global_example_test.go +++ b/global_example_test.go @@ -2,14 +2,15 @@ package log_test import ( "context" + "path/filepath" "gitoa.ru/go-4devs/log" "gitoa.ru/go-4devs/log/level" ) func ExampleDebug() { - logger := log.With(log.New(log.WithStdout()), - log.WithSource(2), + logger := log.New(log.WithStdout()).With( + log.WithSource(2, filepath.Base), log.WithLevel(log.KeyLevel, level.Debug), log.WithExit(level.Alert), log.WithPanic(level.Emergency), @@ -20,5 +21,5 @@ func ExampleDebug() { ctx := context.Background() log.Debug(ctx, "debug message") // Output: - // msg="debug message" source=global_example_test.go:21 level=debug + // msg="debug message" source=global_example_test.go:22 level=debug } diff --git a/logger_example_caller_test.go b/logger_example_caller_test.go index 1b77d0f..b98a2e5 100644 --- a/logger_example_caller_test.go +++ b/logger_example_caller_test.go @@ -1,22 +1,23 @@ package log_test import ( + "path/filepath" + "gitoa.ru/go-4devs/log" "gitoa.ru/go-4devs/log/level" ) func ExampleNew_withCaller() { - logger := log.With( - log.New(log.WithStdout()), - log.WithLevel("level", level.Debug), - log.WithSource(3), + logger := log.New(log.WithStdout()).With( + log.WithLevel(log.KeyLevel, level.Debug), + log.WithSource(3, filepath.Base), ) logger.Err(ctx, "same error message") logger.InfoKVs(ctx, "same info message", "api-version", 0.1) _, _ = logger.Write([]byte("same write message")) // Output: - // msg="same error message" level=error source=logger_example_caller_test.go:14 - // msg="same info message" api-version=0.1 level=info source=logger_example_caller_test.go:15 - // msg="same write message" level=info source=logger_example_caller_test.go:16 + // msg="same error message" level=error source=logger_example_caller_test.go:15 + // msg="same info message" api-version=0.1 level=info source=logger_example_caller_test.go:16 + // msg="same write message" level=info source=logger_example_caller_test.go:17 } diff --git a/logger_example_test.go b/logger_example_test.go index 36ea6f7..3150b66 100644 --- a/logger_example_test.go +++ b/logger_example_test.go @@ -47,19 +47,19 @@ func ExampleNew_errf() { } func ExampleNew_debugKV() { - logger := log.New(log.WithStdout()).With(log.WithLevel("level", level.Debug)) + logger := log.New(log.WithStdout()).With(log.WithLevel(log.KeyLevel, level.Debug)) logger.DebugKVs(ctx, "same message", "error", os.ErrNotExist) // Output: msg="same message" error="file does not exist" level=debug } func ExampleNew_level() { - logger := log.New(log.WithStdout()).With(log.WithLevel("level", level.Error)) + logger := log.New(log.WithStdout()).With(log.WithLevel(log.KeyLevel, level.Error)) logger.Err(ctx, "same error message") // Output: msg="same error message" level=error } func ExampleNew_level_info() { - logger := log.New(log.WithStdout()).With(log.WithLevel("level", level.Error)) + logger := log.New(log.WithStdout()).With(log.WithLevel(log.KeyLevel, level.Error)) logger.Info(ctx, "same message") // Output: } @@ -218,11 +218,11 @@ func ExampleNew_jsonFormat() { } func ExampleNew_textEncoding() { - logger := log.With( - log.New(log.WithStdout()), - log.WithLevel(log.KeyLevel, level.Debug), - log.GoVersion("go-version"), - ) + logger := log.New(log.WithStdout()). + With( + log.WithLevel(log.KeyLevel, level.Debug), + log.GoVersion("go-version"), + ) logger.Err(ctx, "same error message") logger.InfoKVs(ctx, "same info message", "api-version", 0.1, "obj", Obj{Name: "text value", IsEnable: true}) @@ -238,25 +238,29 @@ func (c ctxKey) String() string { } func levelInfo(ctx context.Context, entry *entry.Entry, handler log.Logger) (int, error) { - return handler(ctx, entry.Add(field.String("level", entry.Level().String()))) + return handler(ctx, entry.Add(field.String(log.KeyLevel, entry.Level().String()))) } func ExampleWith() { var requestID ctxKey = "requestID" vctx := context.WithValue(ctx, requestID, "6a5fa048-7181-11ea-bc55-0242ac130003") - logger := log.With( - log.New(log.WithStdout()), - levelInfo, log.WithContextValue(requestID), log.KeyValue("api", "0.1.0"), log.GoVersion("go"), + logger := log.New(log.WithStdout()).With( + levelInfo, + log.WithContextValue(requestID), + log.KeyValue("api", "0.1.0"), + log.GoVersion("go"), ) logger.Info(vctx, "same message") // Output: msg="same message" level=info requestID=6a5fa048-7181-11ea-bc55-0242ac130003 api=0.1.0 go=go1.21.5 } func ExampleLogger_Print() { - logger := log.With( - log.New(log.WithStdout()), - levelInfo, log.KeyValue("client", "http"), log.KeyValue("api", "0.1.0"), log.GoVersion("go"), + logger := log.New(log.WithStdout()).With( + levelInfo, + log.KeyValue("client", "http"), + log.KeyValue("api", "0.1.0"), + log.GoVersion("go"), ) logger.Print("same message") // Output: msg="same message" level=info client=http api=0.1.0 go=go1.21.5 @@ -277,7 +281,7 @@ func Example_fieldClosureFn() { return d }) - log := log.With(log.New(log.WithStdout()), log.WithLevel("level", level.Info)) + log := log.New(log.WithStdout()).With(log.WithLevel(log.KeyLevel, level.Info)) log.DebugKVs(ctx, "debug message", "data", closure) log.ErrKVs(ctx, "error message", "err", closure) @@ -289,7 +293,9 @@ func Example_fieldClosureFn() { } func Example_withGroup() { - log := log.With(log.New(log.WithStdout()), log.WithLevel(log.KeyLevel, level.Info)) + log := log.New(log.WithStdout()).With( + log.WithLevel(log.KeyLevel, level.Info), + ) log.ErrKVs(ctx, "error message", field.Groups("grous_field", diff --git a/logger_test.go b/logger_test.go index cae645b..950aab2 100644 --- a/logger_test.go +++ b/logger_test.go @@ -27,7 +27,7 @@ func TestFields(t *testing.T) { ctx := context.Background() buf := &bytes.Buffer{} log := log.New(log.WithWriter(buf)). - With(log.WithLevel("level", level.Info)) + With(log.WithLevel(log.KeyLevel, level.Info)) success := "msg=message err=\"file already exists\" version=0.1.0 obj={id:uid} closure=\"some closure data\" level=info\n" log.InfoKVs(ctx, "message", @@ -65,7 +65,7 @@ func TestWriter(t *testing.T) { success := "msg=\"info message\" err=\"file already exists\" requestID=6a5fa048-7181-11ea-bc55-0242ac1311113 level=info\n" buf := &bytes.Buffer{} - logger := log.New(log.WithWriter(buf)).With(log.WithContextValue(requestID), log.WithLevel("level", level.Info)) + logger := log.New(log.WithWriter(buf)).With(log.WithContextValue(requestID), log.WithLevel(log.KeyLevel, level.Info)) _, _ = logger.Writer( context.WithValue(ctx, requestID, "6a5fa048-7181-11ea-bc55-0242ac1311113"), @@ -91,7 +91,7 @@ func TestLogger(t *testing.T) { ctx := context.Background() buf := &bytes.Buffer{} - logger := log.New(log.WithWriter(buf)).With(log.WithContextValue(requestID), log.WithLevel("level", level.Info)) + logger := log.New(log.WithWriter(buf)).With(log.WithContextValue(requestID), log.WithLevel(log.KeyLevel, level.Info)) _, err := logger(ctx, nil) if err != nil { diff --git a/middleware.go b/middleware.go index 8464b1d..27936c6 100644 --- a/middleware.go +++ b/middleware.go @@ -85,6 +85,12 @@ func WithContextValue(keys ...fmt.Stringer) Middleware { } } +func WithName(name string) Middleware { + return func(ctx context.Context, data *entry.Entry, handler Logger) (int, error) { + return handler(ctx, data.Replace(KeyName, field.StringValue(name))) + } +} + // WithCaller adds called file. // Deprecated: use WithSource. func WithCaller(key string, depth int, full bool) Middleware { diff --git a/source.go b/source.go index f3fff1d..b033765 100644 --- a/source.go +++ b/source.go @@ -5,28 +5,73 @@ import ( "fmt" "path/filepath" "runtime" + "strings" "gitoa.ru/go-4devs/log/entry" "gitoa.ru/go-4devs/log/field" ) -func WithSource(depth int) Middleware { - const offset = 3 +func WithSource(items int, trimPath func(string) string) Middleware { + const ( + skip = 4 + funcPrefix = "gitoa.ru/go-4devs/log.Logger" + skipHelper = "gitoa.ru/go-4devs/log." + ) + + items += skip return func(ctx context.Context, data *entry.Entry, handler Logger) (int, error) { - pc, file, line, has := runtime.Caller(depth + offset) - if !has { - return handler(ctx, data.AddAny(KeyLevel, field.NilValue())) + pc := make([]uintptr, items) + n := runtime.Callers(skip, pc) + + if n == 0 { + return handler(ctx, data.Add(errSourceField(skip, items))) + } + + pc = pc[:n] // pass only valid pcs to runtime.CallersFrames + frames := runtime.CallersFrames(pc) + prew := false + + for { + frame, more := frames.Next() + + has := strings.HasPrefix(frame.Function, funcPrefix) + if !has && prew { + if strings.HasPrefix(frame.Function, skipHelper) { + continue + } + + return handler(ctx, data.AddAny(KeySource, Source{ + Func: filepath.Base(frame.Function), + Line: frame.Line, + File: trimPath(frame.File), + })) + } + + prew = has + + if !more { + break + } } - fnc := runtime.FuncForPC(pc) + return handler(ctx, data.Add(errSourceField(skip, items))) + } +} + +func TrimPath(file string) string { + idx := strings.LastIndexByte(file, '/') + if idx == -1 { + return filepath.Base(file) + } - return handler(ctx, data.AddAny(KeySource, Source{ - Func: filepath.Base(fnc.Name()), - File: filepath.Base(file), - Line: line, - })) + // Find the penultimate separator. + idx = strings.LastIndexByte(file[:idx], '/') + if idx == -1 { + return filepath.Base(file) } + + return file[idx+1:] } // Source describes the location of a line of source code. @@ -43,3 +88,7 @@ func (l Source) MarshalText() ([]byte, error) { func (l Source) MarshalJSON() ([]byte, error) { return fmt.Appendf([]byte{}, `{"file":"%s","line":%d,"func":"%s"}`, l.File, l.Line, l.Func), nil } + +func errSourceField(skip, max int) field.Field { + return field.String(KeySource, fmt.Sprintf("source not found by frames[%d:%d]", skip, max)) +} diff --git a/source_example_test.go b/source_example_test.go index 82093e7..5af93fa 100644 --- a/source_example_test.go +++ b/source_example_test.go @@ -2,24 +2,25 @@ package log_test import ( "context" + "path/filepath" "gitoa.ru/go-4devs/log" ) func ExampleWithSource() { ctx := context.Background() - logger := log.New(log.WithStdout()).With(log.WithSource(1)) + logger := log.New(log.WithStdout()).With(log.WithSource(1, filepath.Base)) logger.Debug(ctx, "debug message") // Output: - // msg="debug message" source=source_example_test.go:13 + // msg="debug message" source=source_example_test.go:14 } func ExampleWithSource_json() { ctx := context.Background() - logger := log.New(log.WithStdout(), log.WithJSONFormat()).With(log.WithSource(1)) + logger := log.New(log.WithStdout(), log.WithJSONFormat()).With(log.WithSource(2, filepath.Base)) logger.Debug(ctx, "debug message") // Output: - // {"msg":"debug message","source":{"file":"source_example_test.go","line":22,"func":"log_test.ExampleWithSource_json"}} + // {"msg":"debug message","source":{"file":"source_example_test.go","line":23,"func":"log_test.ExampleWithSource_json"}} } diff --git a/writter.go b/writter.go index 68f83bd..0a79e04 100644 --- a/writter.go +++ b/writter.go @@ -25,6 +25,8 @@ const ( // SourceKey is the key used by the built-in handlers for the source file // and line of the log call. The associated value is a string. KeySource = "source" + // KeyName logger name. + KeyName = "name" ) func WithWriter(w io.Writer) func(*option) {