11 Commits

Author SHA1 Message Date
859a8d88f7 Merge pull request 'update ling and go version' (#17) from format into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #17
2024-04-04 12:19:45 +03:00
4baf4b36e7 set golang version
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-04-04 12:17:02 +03:00
andrey
2fd0c1f9ec update ling and go version
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-04-04 12:12:09 +03:00
c7090b5067 Merge pull request 'use dependency format log' (#16) from format into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #16
2024-04-04 12:08:06 +03:00
andrey
16d3e04fd9 use dependency format log 2024-04-04 12:04:45 +03:00
57a908f894 add bracket format (#15)
All checks were successful
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
Reviewed-on: #15
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2024-01-03 21:35:21 +03:00
acaa46b73f update source (#14)
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #14
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2024-01-03 18:44:40 +03:00
24a5d3dd88 Merge pull request 'add source with func name' (#13) from source into master
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build is failing
Reviewed-on: #13
2024-01-02 20:23:08 +03:00
andrey
1de7cc0034 add source with func name
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-01-02 20:22:35 +03:00
9a61d4b9d3 Merge pull request 'add handler zap' (#12) from zap into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #12
2024-01-02 17:33:24 +03:00
andrey
4aef5329c7 add handler zap
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2024-01-02 17:18:18 +03:00
26 changed files with 559 additions and 99 deletions

View File

@@ -5,7 +5,7 @@ name: logger
steps:
- name: test
image: golang:1.21.5
image: golang:1.22.2
volumes:
- name: deps
path: /go/src/mod
@@ -13,60 +13,10 @@ steps:
- go test
- name: golangci-lint
image: golangci/golangci-lint:v1.55
image: golangci/golangci-lint:v1.57
commands:
- golangci-lint run
volumes:
- name: deps
temp: {}
---
kind: pipeline
type: docker
name: otel
steps:
- name: test
image: golang:1.21.5
volumes:
- name: deps
path: /go/src/mod
commands:
- cd handler/otel
- go test
- name: golangci-lint
image: golangci/golangci-lint:v1.55
commands:
- cd handler/otel
- golangci-lint run
volumes:
- name: deps
temp: {}
---
kind: pipeline
type: docker
name: logrus
steps:
- name: test
image: golang:1.21.5
volumes:
- name: deps
path: /go/src/mod
commands:
- cd handler/logrus
- go test
- name: golangci-lint
image: golangci/golangci-lint:v1.55
commands:
- cd handler/logrus
- golangci-lint run
volumes:
- name: deps
temp: {}

View File

@@ -70,3 +70,7 @@ issues:
linters:
- lll
- goerr113
- path: example/*
linters:
- gomnd
- lll

View File

@@ -166,7 +166,9 @@ func BenchmarkDisabledAccumulatedContext(b *testing.B) {
b.Run("4devs/log.Context", func(b *testing.B) {
b.ResetTimer()
logger := NewLogger().With(log.GoVersion("goversion"))
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.InfoKV(ctx, getMessage(0), fakeFields()...)

View File

@@ -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
}

38
example/log.go Normal file
View File

@@ -0,0 +1,38 @@
package main
import (
"context"
"time"
"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), log.WithTime(log.KeyTime, time.RFC3339))
logger.AlertKV(ctx, "alert message new logger", field.String("string", "value"))
service(ctx, logger)
strLogger := log.New(log.WithFormat(log.FormatWithBracket(field.NewEncoderText()))).With(log.WithSource(10, log.TrimPath), log.WithTime(log.KeyTime, time.RFC3339))
strLogger.AlertKV(ctx, "alert message new txt logger", field.String("string", "value"))
service(ctx, strLogger)
}
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")
}

View File

@@ -174,6 +174,7 @@ func needsQuoting(in string) bool {
if char != '\\' && (char == ' ' || char == '=' || !safeSet[char]) {
return true
}
i++
continue

View File

@@ -1,6 +1,7 @@
package field
import (
"encoding"
"fmt"
)
@@ -8,8 +9,18 @@ func NewEncoderText(opts ...func(*BaseEncoder)) BaseEncoder {
opts = append([]func(*BaseEncoder){
WithGropuConfig(0, 0, ' '),
WithNullValue("<nil>"),
WithDefaultValue(func(dst []byte, _ Encoder, val Value) []byte {
return fmt.Appendf(dst, "%+v", val.Any())
WithDefaultValue(func(dst []byte, enc Encoder, val Value) []byte {
switch value := val.Any().(type) {
case encoding.TextMarshaler:
data, err := value.MarshalText()
if err != nil {
return enc.AppendValue(dst, ErrorValue(err))
}
return enc.AppendValue(dst, StringValue(string(data)))
default:
return fmt.Appendf(dst, "%+v", val.Any())
}
}),
}, opts...)

View File

@@ -501,3 +501,14 @@ type Field struct {
func (f Field) String() string {
return fmt.Sprintf("%s=%+v", f.Key, f.Value)
}
// String implent stringer.
func (f Field) IsKey(keys ...string) bool {
for _, key := range keys {
if key == f.Key {
return true
}
}
return false
}

View File

@@ -3,14 +3,15 @@ package log
import (
"context"
"io"
"time"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
)
//nolint:gochecknoglobals
//nolint:gochecknoglobals,gomnd
var global = With(New(),
WithCaller(KeySource, 1, false),
WithTime(KeyTime, time.RFC3339),
WithLevel(KeyLevel, level.Debug),
WithExit(level.Alert),
WithPanic(level.Emergency),

25
global_example_test.go Normal file
View File

@@ -0,0 +1,25 @@
package log_test
import (
"context"
"path/filepath"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/level"
)
func ExampleDebug() {
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),
)
log.SetLogger(logger)
ctx := context.Background()
log.Debug(ctx, "debug message")
// Output:
// msg="debug message" source=global_example_test.go:22 level=debug
}

View File

@@ -11,11 +11,13 @@ import (
)
// Standard create new standart logrus handler.
// Deprecated: delete after 0.7.0
func Standard() log.Logger {
return New(logrus.StandardLogger())
}
// New create new logrus handler.
// Deprecated: delete after 0.7.0
func New(log *logrus.Logger) log.Logger {
return func(ctx context.Context, data *entry.Entry) (int, error) {
lrgFields := make(logrus.Fields, data.Fields().Len())

View File

@@ -7,6 +7,7 @@ import (
"gitoa.ru/go-4devs/log/entry"
)
// Deprecated: delete after 0.7.0
func New() log.Logger {
return func(ctx context.Context, e *entry.Entry) (int, error) {
addEvent(ctx, e)

View File

@@ -7,6 +7,7 @@ import (
"gitoa.ru/go-4devs/log/entry"
)
// Deprecated: delete after 0.7.0
func Middleware() log.Middleware {
return func(ctx context.Context, e *entry.Entry, handler log.Logger) (int, error) {
addEvent(ctx, e)

10
handler/zap/go.mod Normal file
View File

@@ -0,0 +1,10 @@
module gitoa.ru/go-4devs/log/handler/zap
go 1.21.5
require (
gitoa.ru/go-4devs/log v0.5.1
go.uber.org/zap v1.26.0
)
require go.uber.org/multierr v1.10.0 // indirect

16
handler/zap/go.sum Normal file
View File

@@ -0,0 +1,16 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
gitoa.ru/go-4devs/log v0.5.1 h1:rrIyjpUaw8AjDCf7ZuH0HgCRf370O3TV29yrU1xizWM=
gitoa.ru/go-4devs/log v0.5.1/go.mod h1:tREtjEH2cTHl0p3uCVcH9g5tlqtsVNI/tDQVfq53Ty4=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

70
handler/zap/logger.go Normal file
View File

@@ -0,0 +1,70 @@
package zap
import (
"context"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
"go.uber.org/zap"
)
// Deprecated: delete after 0.7.0
func Nop() log.Logger {
return New(zap.NewNop())
}
// Deprecated: delete after 0.7.0
func Example(options ...zap.Option) log.Logger {
return New(zap.NewExample(options...))
}
// Deprecated: delete after 0.7.0
func Production(options ...zap.Option) log.Logger {
z, err := zap.NewProduction(options...)
if err != nil {
panic(err)
}
return New(z)
}
// Deprecated: delete after 0.7.0
func Development(options ...zap.Option) log.Logger {
z, err := zap.NewDevelopment(options...)
if err != nil {
panic(err)
}
return New(z)
}
// New create handler by zap logger.
func New(logger *zap.Logger) log.Logger {
return func(ctx context.Context, data *entry.Entry) (int, error) {
zf := make([]zap.Field, 0, data.Fields().Len())
data.Fields().Fields(func(f field.Field) bool {
zf = append(zf, zap.Any(f.Key, f.Value.Any()))
return true
})
switch data.Level() {
case level.Emergency:
logger.Fatal(data.Message(), zf...)
case level.Alert:
logger.Panic(data.Message(), zf...)
case level.Critical, level.Error:
logger.Error(data.Message(), zf...)
case level.Warning:
logger.Warn(data.Message(), zf...)
case level.Notice, level.Info:
logger.Info(data.Message(), zf...)
case level.Debug:
logger.Debug(data.Message(), zf...)
}
return 0, nil
}
}

View File

@@ -0,0 +1,23 @@
package zap_test
import (
"context"
"io"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/handler/zap"
uzap "go.uber.org/zap"
)
func ExampleNew_zapHandler() {
ctx := context.Background()
log := zap.New(uzap.NewExample())
log.Err(ctx, "log zap")
log.ErrKV(ctx, "log zap kv", field.Int("int", 42))
log.ErrKVs(ctx, "log zap kv sugar", "err", io.EOF)
// Output:
// {"level":"error","msg":"log zap"}
// {"level":"error","msg":"log zap kv","int":42}
// {"level":"error","msg":"log zap kv sugar","err":"EOF"}
}

View File

@@ -0,0 +1,43 @@
package zap_test
import (
"bytes"
"context"
"testing"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
zlog "gitoa.ru/go-4devs/log/handler/zap"
"gitoa.ru/go-4devs/log/level"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func TestNew(t *testing.T) {
t.Parallel()
ctx := context.Background()
buf := &bytes.Buffer{}
core := zapcore.NewCore(zapcore.NewJSONEncoder(zapcore.EncoderConfig{
MessageKey: "msg",
LevelKey: "level",
NameKey: "logger",
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
}), zapcore.AddSync(buf), zapcore.DebugLevel)
logger := zlog.New(zap.New(core))
expect := `{"level":"info","msg":"handle zap message","env":"test"}` + "\n"
if _, err := logger(ctx, entry.New(
entry.WithFields(field.String("env", "test")),
entry.WithLevel(level.Notice),
entry.WithMessage("handle zap message"),
)); err != nil {
t.Error(err)
}
if buf.String() != expect {
t.Errorf("invalid message\n got: %s\nexpect:%s\n", buf.String(), expect)
}
}

View File

@@ -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.WithCaller("caller", 2, false),
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 caller=logger_example_caller_test.go:14
// msg="same info message" api-version=0.1 level=info caller=logger_example_caller_test.go:15
// msg="same write message" level=info caller=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
}

View File

@@ -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:
}
@@ -213,22 +213,22 @@ func ExampleNew_jsonFormat() {
logger.Err(ctx, "same error message")
logger.WarnKVs(ctx, "same warn message", "obj", Obj{Name: "obj name"})
// Output:
// {"msg":"same error message","level":"error","go-version":"go1.21.5"}
// {"msg":"same warn message","obj":{"Name":"obj name","IsEnable":false},"level":"warning","go-version":"go1.21.5"}
// {"msg":"same error message","level":"error","go-version":"go1.22.2"}
// {"msg":"same warn message","obj":{"Name":"obj name","IsEnable":false},"level":"warning","go-version":"go1.22.2"}
}
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})
// Output:
// msg="same error message" level=error go-version=go1.21.5
// msg="same info message" api-version=0.1 obj={Name:text value IsEnable:true} level=info go-version=go1.21.5
// msg="same error message" level=error go-version=go1.22.2
// msg="same info message" api-version=0.1 obj={Name:text value IsEnable:true} level=info go-version=go1.22.2
}
type ctxKey string
@@ -238,28 +238,32 @@ 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
// Output: msg="same message" level=info requestID=6a5fa048-7181-11ea-bc55-0242ac130003 api=0.1.0 go=go1.22.2
}
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
// Output: msg="same message" level=info client=http api=0.1.0 go=go1.22.2
}
func ExamplePrint() {
@@ -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",

View File

@@ -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 {

View File

@@ -38,6 +38,7 @@ func With(logger Logger, mw ...Middleware) Logger {
if curI == lastI {
return logger(currentCtx, currentEntry)
}
curI++
n, err := mw[curI](currentCtx, currentEntry, chainHandler)
curI--
@@ -85,7 +86,14 @@ 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 {
const offset = 2

94
source.go Normal file
View File

@@ -0,0 +1,94 @@
package log
import (
"context"
"fmt"
"path/filepath"
"runtime"
"strings"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
)
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 := 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
}
}
return handler(ctx, data.Add(errSourceField(skip, items)))
}
}
func TrimPath(file string) string {
idx := strings.LastIndexByte(file, '/')
if idx == -1 {
return filepath.Base(file)
}
// 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.
type Source struct {
Func string `json:"func"`
File string `json:"file"`
Line int `json:"line"`
}
func (l Source) MarshalText() ([]byte, error) {
return []byte(fmt.Sprintf("%s:%d", l.File, l.Line)), nil
}
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))
}

26
source_example_test.go Normal file
View File

@@ -0,0 +1,26 @@
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, filepath.Base))
logger.Debug(ctx, "debug message")
// Output:
// 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(2, filepath.Base))
logger.Debug(ctx, "debug message")
// Output:
// {"msg":"debug message","source":{"file":"source_example_test.go","line":23,"func":"log_test.ExampleWithSource_json"}}
}

37
writer_example_test.go Normal file
View File

@@ -0,0 +1,37 @@
package log_test
import (
"context"
"math"
"path/filepath"
"time"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
)
func exampleWithTime(key, format string) log.Middleware {
return func(ctx context.Context, e *entry.Entry, handler log.Logger) (int, error) {
return handler(ctx, e.Add(field.FormatTime(key, format, time.Unix(math.MaxInt32, 0).In(time.UTC))))
}
}
func ExampleFormatWithBracket() {
ctx := context.Background()
logger := log.New(log.WithFormat(log.FormatWithBracket(field.NewEncoderText())), log.WithStdout()).With(
log.WithSource(10, filepath.Base),
// log.WithTime(log.KeyTime, time.RFC3339),
exampleWithTime(log.KeyTime, time.RFC3339),
log.WithLevel(log.KeyLevel, level.Info),
)
logger.InfoKV(ctx, "imfo message", field.Int64("num", 42))
serviceLogger := logger.With(log.WithName("service_name"))
serviceLogger.Err(ctx, "error message")
// Output:
// 2038-01-19T03:14:07Z [info] writer_example_test.go:30 "imfo message" num=42
// 2038-01-19T03:14:07Z [error][service_name] writer_example_test.go:33 "error message"
}

View File

@@ -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) {
@@ -41,15 +43,18 @@ func WithStdout() func(*option) {
// WithStringFormat sets format as simple string.
func WithStringFormat() func(*option) {
return func(o *option) {
o.format = formatText()
}
return WithFormat(FormatString(field.NewEncoderText()))
}
// WithJSONFormat sets json output format.
func WithJSONFormat() func(*option) {
return WithFormat(FormatJSON(field.NewEncoderJSON()))
}
// WithFormat sets custom output format.
func WithFormat(format func(io.Writer, *entry.Entry) (int, error)) func(*option) {
return func(o *option) {
o.format = formatJSON()
o.format = format
}
}
@@ -61,7 +66,7 @@ type option struct {
// New creates standart logger.
func New(opts ...func(*option)) Logger {
log := option{
format: formatText(),
format: FormatString(field.NewEncoderText()),
out: os.Stderr,
}
@@ -74,9 +79,64 @@ func New(opts ...func(*option)) Logger {
}
}
func formatText() func(io.Writer, *entry.Entry) (int, error) {
enc := field.NewEncoderText()
type Encoder interface {
AppendValue(dst []byte, val field.Value) []byte
AppendField(dst []byte, val field.Field) []byte
}
func FormatWithBracket(enc Encoder) func(io.Writer, *entry.Entry) (int, error) {
appendValue := func(buf *buffer.Buffer, data field.Fields, key, prefix, suffix string) *buffer.Buffer {
data.Fields(
func(f field.Field) bool {
if f.IsKey(key) {
_, _ = buf.WriteString(prefix)
*buf = enc.AppendValue(*buf, f.Value)
_, _ = buf.WriteString(suffix)
return false
}
return true
})
return buf
}
return func(w io.Writer, data *entry.Entry) (int, error) {
buf := buffer.New()
defer func() {
buf.Free()
}()
fields := data.Fields()
buf = appendValue(buf, fields, KeyTime, "", " ")
_, _ = buf.WriteString("[")
*buf = enc.AppendValue(*buf, field.StringValue(data.Level().String()))
_, _ = buf.WriteString("]")
buf = appendValue(buf, fields, KeyName, "[", "]")
buf = appendValue(buf, fields, KeySource, " ", " ")
*buf = enc.AppendValue(*buf, field.StringValue(data.Message()))
fields.Fields(func(f field.Field) bool {
if !f.IsKey(KeyTime, KeySource, KeyName, KeyLevel) {
*buf = enc.AppendField(*buf, f)
}
return true
})
_, _ = buf.WriteString("\n")
n, err := w.Write(*buf)
if err != nil {
return 0, fmt.Errorf("format text:%w", err)
}
return n, nil
}
}
func FormatString(enc Encoder) func(io.Writer, *entry.Entry) (int, error) {
return func(w io.Writer, entry *entry.Entry) (int, error) {
buf := buffer.New()
defer func() {
@@ -100,9 +160,7 @@ func formatText() func(io.Writer, *entry.Entry) (int, error) {
}
}
func formatJSON() func(w io.Writer, entry *entry.Entry) (int, error) {
enc := field.NewEncoderJSON()
func FormatJSON(enc Encoder) func(w io.Writer, entry *entry.Entry) (int, error) {
return func(w io.Writer, entry *entry.Entry) (int, error) {
buf := buffer.New()
defer func() {