From 01d5d39527dda45fdcd5a97b90f53cc5df0a3cf8 Mon Sep 17 00:00:00 2001 From: andrey Date: Wed, 3 Jan 2024 18:20:50 +0300 Subject: [PATCH] update source --- .golangci.yml | 3 ++ entry/entry.go | 21 +++++++++++ example/log.go | 33 ++++++++++++++++ global.go | 2 +- global_example_test.go | 4 +- logger_example_caller_test.go | 11 +++--- logger_example_test.go | 40 +++++++++++--------- logger_test.go | 6 +-- middleware.go | 6 +++ source.go | 71 +++++++++++++++++++++++++++++------ source_example_test.go | 6 +-- writter.go | 2 + 12 files changed, 162 insertions(+), 43 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..aaac0f1 --- /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)) + 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..5be042a 100644 --- a/global.go +++ b/global.go @@ -8,7 +8,7 @@ import ( "gitoa.ru/go-4devs/log/level" ) -//nolint:gochecknoglobals +//nolint:gochecknoglobals,gomnd var global = With(New(), WithSource(2), WithLevel(KeyLevel, level.Debug), diff --git a/global_example_test.go b/global_example_test.go index 882f70b..5044998 100644 --- a/global_example_test.go +++ b/global_example_test.go @@ -8,7 +8,7 @@ import ( ) func ExampleDebug() { - logger := log.With(log.New(log.WithStdout()), + logger := log.New(log.WithStdout()).With( log.WithSource(2), log.WithLevel(log.KeyLevel, level.Debug), log.WithExit(level.Alert), @@ -20,5 +20,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=log/global_example_test.go:21 level=debug } diff --git a/logger_example_caller_test.go b/logger_example_caller_test.go index 1b77d0f..884d30c 100644 --- a/logger_example_caller_test.go +++ b/logger_example_caller_test.go @@ -6,9 +6,8 @@ import ( ) func ExampleNew_withCaller() { - logger := log.With( - log.New(log.WithStdout()), - log.WithLevel("level", level.Debug), + logger := log.New(log.WithStdout()).With( + log.WithLevel(log.KeyLevel, level.Debug), log.WithSource(3), ) logger.Err(ctx, "same error message") @@ -16,7 +15,7 @@ func ExampleNew_withCaller() { _, _ = 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=log/logger_example_caller_test.go:13 + // msg="same info message" api-version=0.1 level=info source=log/logger_example_caller_test.go:14 + // msg="same write message" level=info source=log/logger_example_caller_test.go:15 } 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..caa8c8e 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) 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..c780f3a 100644 --- a/source_example_test.go +++ b/source_example_test.go @@ -12,14 +12,14 @@ func ExampleWithSource() { logger.Debug(ctx, "debug message") // Output: - // msg="debug message" source=source_example_test.go:13 + // msg="debug message" source=log/source_example_test.go:13 } 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)) 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":"log/source_example_test.go","line":22,"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) {