21 Commits

Author SHA1 Message Date
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
eb1708a296 Merge pull request 'add logrus handler' (#11) from logrus into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #11
2024-01-02 17:14:38 +03:00
andrey
50cfee751d add logrus handler
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-01-02 17:03:49 +03:00
722669f094 add otel as separate module (#10)
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #10
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2024-01-02 16:56:49 +03:00
d365c5b36b Merge pull request 'remove handlers' (#9) from handlers into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #9
2024-01-02 15:59:46 +03:00
andrey
e7774808fd remove handlers
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build is failing
continuous-integration/drone/pr Build is failing
2024-01-02 15:58:24 +03:00
abbcf0b1a0 update field (#8)
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build is failing
Reviewed-on: #8
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2024-01-02 15:45:34 +03:00
a3091c4eb6 update level (#7)
Some checks failed
continuous-integration/drone/push Build is failing
- add TextUnmarshaler
- add TextMarshaler
- update json.Marshaler
- update json.Unmarshaler
- update string name Emergency and Critical
- add test json Unmarshaler and Marshaler

Reviewed-on: #7
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2024-01-01 17:00:37 +03:00
andrey
45d51e4825 update libs
Some checks failed
continuous-integration/drone/push Build is failing
2023-09-19 10:24:42 +03:00
81eaf8c8b2 update opentelemetry version (#6)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Co-authored-by: andrey1s <andrey@4devs.pro>
Reviewed-on: #6
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2022-09-06 22:25:04 +03:00
7570ade82a upddate caller write method (#5)
All checks were successful
continuous-integration/drone/push Build is passing
Co-authored-by: andrey1s <andrey@4devs.pro>
Reviewed-on: #5
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2022-03-14 09:58:23 +03:00
012e2ce197 Merge pull request 'update golang' (#4) from fields into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #4
2022-03-13 13:17:01 +03:00
andrey1s
51b844d50b update golang
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-03-13 12:01:36 +03:00
f70653066a Merge pull request 'add test append fields' (#2) from fields into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #2
2022-03-13 11:10:16 +03:00
andrey1s
21c14e24be update go version
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-12 20:00:01 +03:00
andrey1s
d7310ed1df add test append fields 2022-03-12 10:01:11 +03:00
b80e6d1a29 Delete '.DS_Store' 2022-02-04 22:48:48 +03:00
b56ca08811 update zap/logrus hanler (#1)
Co-authored-by: andrey1s <andrey_simfi@list.ru>
Reviewed-on: #1
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2022-01-02 14:32:19 +03:00
andrey1s
deb67b0008 restore v0.3.0 2021-09-19 17:25:47 +03:00
57 changed files with 3987 additions and 381 deletions

View File

@@ -1,9 +1,11 @@
---
kind: pipeline
name: default
type: docker
name: logger
steps:
- name: test
image: golang:1.14.2
image: golang:1.21.5
volumes:
- name: deps
path: /go/src/mod
@@ -11,40 +13,85 @@ steps:
- go test
- name: golangci-lint
image: golangci/golangci-lint:v1.29
image: golangci/golangci-lint:v1.55
commands:
- golangci-lint run
- name: logrus golangci-lint
image: golangci/golangci-lint:v1.29
commands:
- cd logrus
- golangci-lint run
- name: logrus test
image: golang:1.14.2
volumes:
- name: deps
path: /go/src/mod
commands:
- cd logrus
- go test
- name: zap golangci-lint
image: golangci/golangci-lint:v1.29
commands:
- cd zap
- golangci-lint run
- name: zap test
image: golang:1.14.2
volumes:
- name: deps
path: /go/src/mod
commands:
- cd zap
- go test
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: {}
---
kind: pipeline
type: docker
name: zap
steps:
- name: test
image: golang:1.21.5
volumes:
- name: deps
path: /go/src/mod
commands:
- cd handler/zap
- go test
- name: golangci-lint
image: golangci/golangci-lint:v1.55
commands:
- cd handler/zap
- golangci-lint run
volumes:
- name: deps
temp: {}

View File

@@ -1,3 +1,6 @@
run:
timeout: 5m
linters-settings:
dupl:
threshold: 100
@@ -9,21 +12,50 @@ linters-settings:
min-occurrences: 2
gocyclo:
min-complexity: 15
golint:
min-confidence: 0
govet:
check-shadowing: true
lll:
line-length: 140
maligned:
fieldalignment:
suggest-new: true
misspell:
locale: US
exhaustive:
default-signifies-exhaustive: true
varnamelen:
min-name-length: 2
ignore-names:
- err
- n
- i
- w
tagliatelle:
case:
use-field-name: true
rules:
json: snake
yaml: camel
xml: camel
bson: camel
avro: snake
linters:
enable-all: true
disable:
# deprecated
- interfacer
- structcheck
- varcheck
- golint
- deadcode
- scopelint
- exhaustivestruct
- ifshort
- nosnakecase
- maligned
- depguard # need configure
- nolintlint # use with space
issues:
# Excluding configuration per-path, per-linter, per-text and per-source
@@ -31,3 +63,10 @@ issues:
- path: _test\.go
linters:
- gomnd
- ireturn
- exhaustruct
- gochecknoglobals
- path: _example_test\.go
linters:
- lll
- goerr113

View File

@@ -1,4 +1,4 @@
MIT License Copyright (c) 2020 go-4devs
MIT License Copyright (c) 2020-2022 4devs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

176
bench_test.go Normal file
View File

@@ -0,0 +1,176 @@
package log_test
import (
"context"
"errors"
"fmt"
"io"
"testing"
"time"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/field"
)
var (
errExample = errors.New("fail")
_messages = fakeMessages(1000)
_tenInts = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
_tenStrings = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
_tenTimes = []time.Time{
time.Unix(0, 0),
time.Unix(1, 0),
time.Unix(2, 0),
time.Unix(3, 0),
time.Unix(4, 0),
time.Unix(5, 0),
time.Unix(6, 0),
time.Unix(7, 0),
time.Unix(8, 0),
time.Unix(9, 0),
}
_oneUser = &user{
Name: "Jane Doe",
Email: "jane@test.com",
CreatedAt: time.Date(1980, 1, 1, 12, 0, 0, 0, time.UTC),
}
_tenUsers = users{
_oneUser,
_oneUser,
_oneUser,
_oneUser,
_oneUser,
_oneUser,
_oneUser,
_oneUser,
_oneUser,
_oneUser,
}
)
type user struct {
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
type users []*user
func fakeMessages(n int) []string {
messages := make([]string, n)
for i := range messages {
messages[i] = fmt.Sprintf("Test logging, but use a somewhat realistic message length. (#%v)", i)
}
return messages
}
func getMessage(iter int) string {
return _messages[iter%1000]
}
func fakeFmtArgs() []interface{} {
// Need to keep this a function instead of a package-global var so that we
// pay the cast-to-interface{} penalty on each call.
return []interface{}{
_tenInts[0],
_tenInts,
_tenStrings[0],
_tenStrings,
_tenTimes[0],
_tenTimes,
_oneUser,
_oneUser,
_tenUsers,
errExample,
}
}
func fakeFields() []field.Field {
return []field.Field{
field.Int("int", _tenInts[0]),
field.Ints("ints", _tenInts...),
field.String("string", _tenStrings[0]),
field.Strings("strings", _tenStrings...),
field.Time("time", _tenTimes[0]),
field.Times("times", _tenTimes...),
field.Any("user1", _oneUser),
field.Any("user2", _oneUser),
field.Any("users", _tenUsers),
field.Error("err", errExample),
}
}
func fakeSugarFields() []interface{} {
return []interface{}{
"int", _tenInts[0],
"ints", _tenInts,
"string", _tenStrings[0],
"strings", _tenStrings,
"time", _tenTimes[0],
"times", _tenTimes,
"user1", _oneUser,
"user2", _oneUser,
"users", _tenUsers,
"error", errExample,
}
}
func NewLogger() log.Logger {
return log.New(log.WithWriter(io.Discard))
}
func BenchmarkDisabledWithoutFields(b *testing.B) {
ctx := context.Background()
logger := NewLogger()
b.Run("4devs/log", func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.Info(ctx, getMessage(0))
}
})
})
b.Run("4devs/log.Formatting", func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.Infof(ctx, "%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...)
}
})
})
}
func BenchmarkDisabledAccumulatedContext(b *testing.B) {
ctx := context.Background()
logger := NewLogger()
b.Run("4devs/log", func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.InfoKV(ctx, getMessage(0), fakeFields()...)
}
})
})
b.Run("4devs/log.Sugar", func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logger.InfoKVs(ctx, getMessage(1), fakeSugarFields()...)
}
})
})
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()...)
}
})
})
}

22
entry/caller.go Normal file
View File

@@ -0,0 +1,22 @@
package entry
import (
"path/filepath"
"runtime"
"strconv"
)
func Caller(depth int, full bool) string {
const offset = 3
_, file, line, has := runtime.Caller(depth + offset)
if !has {
file, line = "???", 0
}
if !full && has {
file = filepath.Base(file)
}
return file + ":" + strconv.Itoa(line)
}

163
entry/entry.go Normal file
View File

@@ -0,0 +1,163 @@
package entry
import (
"fmt"
"strings"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
)
const (
defaultCap = 5
)
type Option func(*Entry)
func WithCapacity(c int) Option {
return func(e *Entry) {
e.fields = make(field.Fields, 0, c+1)
}
}
func WithFields(fields ...field.Field) Option {
return func(e *Entry) {
e.fields = fields
}
}
func WithMessage(msg string) Option {
return func(e *Entry) {
e.format = msg
}
}
func WithMessagef(format string, args ...interface{}) Option {
return func(e *Entry) {
e.format = format
e.args = args
}
}
func WithLevel(lvl level.Level) Option {
return func(e *Entry) {
e.level = lvl
}
}
func New(opts ...Option) *Entry {
entry := &Entry{
fields: make(field.Fields, 0, defaultCap+1),
level: level.Debug,
format: "",
args: make([]interface{}, 0, defaultCap+1),
}
for _, opt := range opts {
opt(entry)
}
return entry
}
// Entry slice field.
type Entry struct {
format string
args []interface{}
level level.Level
fields field.Fields
}
func (e *Entry) Reset() {
e.fields = e.fields[:0]
e.args = e.args[:0]
e.format = ""
}
func (e *Entry) Fields() field.Fields {
return e.fields
}
// String implement stringer.
func (e *Entry) String() string {
if e == nil {
return ""
}
str := make([]string, len(e.fields)+1)
str[0] = e.Message()
for i, field := range e.fields {
str[i+1] = field.String()
}
return strings.Join(str, " ")
}
func (e *Entry) Message() string {
switch {
case len(e.args) > 0 && e.format != "":
return fmt.Sprintf(e.format, e.args...)
case len(e.args) > 0:
return fmt.Sprint(e.args...)
default:
return e.format
}
}
func (e *Entry) Level() level.Level {
if e == nil {
return level.Debug
}
return e.level
}
func (e *Entry) SetLevel(level level.Level) *Entry {
if e == nil {
return New().SetLevel(level)
}
e.level = level
return e
}
func (e *Entry) SetMessage(msg string) *Entry {
if e == nil {
return New().SetMessage(msg)
}
e.format = msg
return e
}
func (e *Entry) SetMessagef(format string, args ...interface{}) *Entry {
if e == nil {
return New().SetMessagef(format, args...)
}
e.format = format
e.args = append(e.args[:0], args...)
return e
}
func (e *Entry) Add(fields ...field.Field) *Entry {
if e == nil {
return New(WithFields(fields...))
}
e.fields = e.fields.Append(fields...)
return e
}
func (e *Entry) AddAny(key string, value interface{}) *Entry {
return e.Add(field.Any(key, value))
}
func (e *Entry) AddString(key, value string) *Entry {
return e.Add(field.String(key, value))
}

22
entry/pool.go Normal file
View File

@@ -0,0 +1,22 @@
package entry
import "sync"
//nolint:gochecknoglobals
var pool = sync.Pool{
New: func() interface{} {
return New()
},
}
//nolint:forcetypeassert
func Get() *Entry {
e := pool.Get().(*Entry)
e.Reset()
return e
}
func Put(e *Entry) {
pool.Put(e)
}

View File

@@ -1,40 +1,15 @@
package log
import (
"fmt"
"strings"
"gitoa.ru/go-4devs/log/field"
)
// Fields slice field.
type Fields []Field
// String implement stringer.
func (f Fields) String() string {
str := make([]string, len(f))
for i, field := range f {
str[i] = field.String()
}
return strings.Join(str, " ")
}
// NewField create field.
func NewField(key string, value interface{}) Field {
return Field{Key: key, Value: value}
}
// Field struct.
type Field struct {
Key string
Value interface{}
}
// String implent stringer.
func (f Field) String() string {
return fmt.Sprintf("%s=%+v", f.Key, f.Value)
// Field create field.
func Field(key string, value interface{}) field.Field {
return field.Any(key, value)
}
// FieldError new errors field with key error.
func FieldError(err error) Field {
return NewField("error", err)
func FieldError(err error) field.Field {
return field.Error("error", err)
}

281
field/encoder.go Normal file
View File

@@ -0,0 +1,281 @@
//nolint:gomnd
package field
import (
"fmt"
"strconv"
"time"
"unicode"
"unicode/utf8"
)
type Encoder interface {
AppendField(dst []byte, field Field) []byte
AppendValue(dst []byte, val Value) []byte
}
func WithAppendString(fn func(dst []byte, in string) []byte) func(*BaseEncoder) {
return func(be *BaseEncoder) {
be.AppendString = fn
}
}
func WithNullValue(in string) func(*BaseEncoder) {
return func(be *BaseEncoder) {
be.nullValue = []byte(in)
}
}
func WithDelimeter(in byte) func(*BaseEncoder) {
return func(be *BaseEncoder) {
be.delimeter = in
}
}
func WithGropuConfig(start, end, deli byte) func(*BaseEncoder) {
return func(be *BaseEncoder) {
be.group = groupConfig{
start: start,
end: end,
deli: deli,
}
}
}
func WithDefaultValue(fn func(dst []byte, e Encoder, val Value) []byte) func(*BaseEncoder) {
return func(be *BaseEncoder) {
be.DefaultValue = fn
}
}
func NewEncoder(opts ...func(*BaseEncoder)) BaseEncoder {
be := BaseEncoder{
nullValue: []byte("null"),
group: groupConfig{
start: '{',
end: '}',
deli: ',',
},
array: groupConfig{
start: '[',
end: ']',
deli: ',',
},
timeFormat: time.RFC3339,
AppendString: AppendString,
delimeter: '=',
DefaultValue: func(dst []byte, e Encoder, val Value) []byte {
return e.AppendValue(dst, StringValue(fmt.Sprintf("%+v", val.Any())))
},
}
for _, opt := range opts {
opt(&be)
}
return be
}
type groupConfig struct {
start byte
end byte
deli byte
}
type BaseEncoder struct {
nullValue []byte
group groupConfig
array groupConfig
timeFormat string
AppendString func(dst []byte, in string) []byte
delimeter byte
DefaultValue func(dst []byte, e Encoder, val Value) []byte
}
func (b BaseEncoder) AppendValue(dst []byte, val Value) []byte {
return b.appendValue(dst, val, "", 0)
}
func (b BaseEncoder) AppendDelimiter(dst []byte, deli byte) []byte {
if deli == 0 {
return dst
}
return append(dst, deli)
}
//nolint:gocyclo,cyclop
func (b BaseEncoder) appendValue(dst []byte, val Value, prefix string, deli byte) []byte {
switch val.Kind {
case KindGroup:
return b.appendGroup(dst, val.AsGroup(), prefix)
case KindClosure:
return b.appendValue(dst, AnyValue(val.Resolve()), prefix, deli)
case KindArray:
return b.AppendArray(b.AppendDelimiter(dst, deli), val.AsArray())
case KindNil:
return b.AppendNull(b.AppendDelimiter(dst, deli))
case KindBool:
return b.AppendBool(b.AppendDelimiter(dst, deli), val.AsBool())
case KindBinary:
return b.AppendBytes(b.AppendDelimiter(dst, deli), val.AsBinary())
case KindComplex128:
return b.AppendComplex(b.AppendDelimiter(dst, deli), val.AsComplex128())
case KindInt64:
return b.AppendInt(b.AppendDelimiter(dst, deli), val.AsInt64())
case KindFloat32:
return b.AppendFloat(b.AppendDelimiter(dst, deli), float64(val.AsFloat32()), 32)
case KindFloat64:
return b.AppendFloat(b.AppendDelimiter(dst, deli), val.AsFloat64(), 64)
case KindUint64:
return b.AppendUint(b.AppendDelimiter(dst, deli), val.AsUint64())
case KindError:
return b.AppendString(b.AppendDelimiter(dst, deli), val.AsError().Error())
case KindString:
return b.AppendString(b.AppendDelimiter(dst, deli), val.AsString())
case KindDuration:
return b.AppendDuration(b.AppendDelimiter(dst, deli), val.AsDuration())
case KindTime:
return b.AppendTime(b.AppendDelimiter(dst, deli), val.AsTime())
case KindAny:
return b.DefaultValue(b.AppendDelimiter(dst, deli), b, val)
}
return b.DefaultValue(b.AppendDelimiter(dst, deli), b, val)
}
func (b BaseEncoder) AppendDuration(dst []byte, d time.Duration) []byte {
return b.AppendString(dst, d.String())
}
func (b BaseEncoder) AppendTime(dst []byte, t time.Time) []byte {
return b.AppendString(dst, t.Format(b.timeFormat))
}
func AppendString(dst []byte, in string) []byte {
if needsQuoting(in) {
return strconv.AppendQuote(dst, in)
}
return append(dst, in...)
}
//nolint:cyclop
func needsQuoting(in string) bool {
if len(in) == 0 {
return true
}
for i := 0; i < len(in); {
char := in[i]
if char < utf8.RuneSelf {
// Quote anything except a backslash that would need quoting in a
// JSON string, as well as space and '='
if char != '\\' && (char == ' ' || char == '=' || !safeSet[char]) {
return true
}
i++
continue
}
decodeRune, size := utf8.DecodeRuneInString(in[i:])
if decodeRune == utf8.RuneError || unicode.IsSpace(decodeRune) || !unicode.IsPrint(decodeRune) {
return true
}
i += size
}
return false
}
func (b BaseEncoder) AppendField(dst []byte, field Field) []byte {
prefix := ""
if len(dst) != 0 {
prew := dst[len(dst)-1]
if prew != '{' && prew != '.' {
prefix = string(b.group.deli)
}
}
return b.appendField(dst, field, prefix, b.delimeter)
}
func (b BaseEncoder) appendField(dst []byte, field Field, prefix string, deli byte) []byte {
dst = b.AppendKey(dst, field.Key, prefix)
return b.appendValue(dst, field.Value, field.Key+".", deli)
}
func (b BaseEncoder) AppendKey(dst []byte, key string, prefix string) []byte {
if prefix != "" {
dst = append(dst, prefix...)
}
return b.AppendString(dst, key)
}
func (b BaseEncoder) AppendComplex(dst []byte, c complex128) []byte {
cmplx := strconv.FormatComplex(c, 'g', -1, 128)
return b.AppendString(dst, cmplx)
}
func (b BaseEncoder) AppendFloat(dst []byte, f float64, bitSize int) []byte {
return strconv.AppendFloat(dst, f, 'g', -1, bitSize)
}
func (b BaseEncoder) AppendUint(dst []byte, u uint64) []byte {
return strconv.AppendUint(dst, u, 10)
}
func (b BaseEncoder) AppendNull(dst []byte) []byte {
return append(dst, b.nullValue...)
}
func (b BaseEncoder) AppendInt(dst []byte, val int64) []byte {
return strconv.AppendInt(dst, val, 10)
}
func (b BaseEncoder) AppendBool(dst []byte, val bool) []byte {
return strconv.AppendBool(dst, val)
}
func (b BaseEncoder) AppendGroup(dst []byte, fields []Field) []byte {
dst = append(dst, b.group.start)
dst = b.appendGroup(dst, fields, "")
return append(dst, b.group.end)
}
func (b BaseEncoder) appendGroup(dst []byte, fields []Field, prefix string) []byte {
if len(fields) > 0 {
dst = b.appendField(dst, fields[0], ".", b.delimeter)
for _, field := range fields[1:] {
dst = b.appendField(append(dst, b.group.deli), field, prefix, b.delimeter)
}
}
return dst
}
func (b BaseEncoder) AppendArray(dst []byte, in []Value) []byte {
dst = append(dst, b.array.start)
if len(in) > 0 {
dst = b.appendValue(dst, in[0], "", 0)
for _, value := range in[1:] {
dst = b.appendValue(append(dst, b.array.deli), value, "", 0)
}
}
return append(dst, b.array.end)
}
func (b BaseEncoder) AppendBytes(dst, in []byte) []byte {
dst = append(dst, '"')
dst = append(dst, in...)
return append(dst, '"')
}

23
field/encoder_json.go Normal file
View File

@@ -0,0 +1,23 @@
package field
import (
"encoding/json"
"strconv"
)
func NewEncoderJSON(opts ...func(*BaseEncoder)) BaseEncoder {
opts = append([]func(*BaseEncoder){
WithAppendString(strconv.AppendQuote),
WithDelimeter(':'),
WithDefaultValue(func(dst []byte, e Encoder, val Value) []byte {
js, err := json.Marshal(val.Any())
if err != nil {
return e.AppendValue(dst, ErrorValue(err))
}
return append(dst, js...)
}),
}, opts...)
return NewEncoder(opts...)
}

View File

@@ -0,0 +1,38 @@
package field_test
import (
"testing"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/internal/buffer"
)
func TestEncoderJSONAppendField_string(t *testing.T) {
t.Parallel()
const expect = `"array":["value","other"],"str":"value","nullableStr":"value","nullstr":null`
encode := field.NewEncoderJSON()
buf := buffer.New()
defer func() {
buf.Free()
}()
val := "value"
strs := field.Strings("array", val, "other")
*buf = encode.AppendField(*buf, strs)
str := field.String("str", val)
*buf = encode.AppendField(*buf, str)
strp := field.Stringp("nullableStr", &val)
*buf = encode.AppendField(*buf, strp)
nullStr := field.Stringp("nullstr", nil)
*buf = encode.AppendField(*buf, nullStr)
if buf.String() != expect {
t.Errorf("json string expect:%v got:%s", expect, buf)
}
}

28
field/encoder_text.go Normal file
View File

@@ -0,0 +1,28 @@
package field
import (
"encoding"
"fmt"
)
func NewEncoderText(opts ...func(*BaseEncoder)) BaseEncoder {
opts = append([]func(*BaseEncoder){
WithGropuConfig(0, 0, ' '),
WithNullValue("<nil>"),
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...)
return NewEncoder(opts...)
}

5
field/errors.go Normal file
View File

@@ -0,0 +1,5 @@
package field
import "errors"
var ErrUndefined = errors.New("indefined")

503
field/field.go Normal file
View File

@@ -0,0 +1,503 @@
package field
import (
"fmt"
"time"
)
func Any(key string, value any) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func String(key, value string) Field {
return Field{
Key: key,
Value: StringValue(value),
}
}
func Stringp(key string, value *string) Field {
return Field{
Key: key,
Value: StringpValue(value),
}
}
func Strings(key string, value ...string) Field {
return Field{
Key: key,
Value: StringsValue(value),
}
}
func Bool(key string, value bool) Field {
return Field{
Key: key,
Value: BoolValue(value),
}
}
func Bools(key string, value ...bool) Field {
return Field{
Key: key,
Value: BoolsValue(value),
}
}
func Boolp(key string, value *bool) Field {
return Field{
Key: key,
Value: BoolpValue(value),
}
}
func Uint(key string, value uint) Field {
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uints(key string, value ...uint) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uintp(key string, value *uint) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint8(key string, value uint8) Field {
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uint8s(key string, value ...uint8) Field {
return Field{
Key: key,
Value: Uint8sValue(value),
}
}
func Uint8p(key string, value *uint8) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint16(key string, value uint16) Field {
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uint16s(key string, value ...uint16) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint16p(key string, value *uint16) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint32(key string, value uint32) Field {
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uint32s(key string, value ...uint32) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint32p(key string, value *uint32) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint64(key string, value uint64) Field {
return Field{
Key: key,
Value: Uint64Value(value),
}
}
func Uint64s(key string, value ...uint64) Field {
return Field{
Key: key,
Value: Uint64sValue(value),
}
}
func Uint64p(key string, value *uint64) Field {
return Field{
Key: key,
Value: Uint64pValue(value),
}
}
func Int(key string, value int) Field {
return Field{
Key: key,
Value: Int64Value(int64(value)),
}
}
func Ints(key string, value ...int) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Intp(key string, value *int) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int8(key string, value int8) Field {
return Field{
Key: key,
Value: Int64Value(int64(value)),
}
}
func Int8s(key string, value ...int8) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int8p(key string, value *int8) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int16(key string, value int16) Field {
return Field{
Key: key,
Value: Int64Value(int64(value)),
}
}
func Int16s(key string, value ...int16) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int16p(key string, value *int16) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int32(key string, value int32) Field {
return Field{
Key: key,
Value: Int64Value(int64(value)),
}
}
func Int32s(key string, value ...int32) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int32p(key string, value *int32) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int64(key string, value int64) Field {
return Field{
Key: key,
Value: Int64Value(value),
}
}
func Int64s(key string, value ...int64) Field {
return Field{
Key: key,
Value: Int64sValue(value),
}
}
func Int64p(key string, value *int64) Field {
return Field{
Key: key,
Value: Int64pValue(value),
}
}
func Float32(key string, value float32) Field {
return Field{
Key: key,
Value: Float64Value(float64(value)),
}
}
func Float32s(key string, value ...float32) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Float32p(key string, value *float32) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Float64(key string, value float64) Field {
return Field{
Key: key,
Value: Float64Value(value),
}
}
func Float64s(key string, value ...float64) Field {
return Field{
Key: key,
Value: Float64sValue(value),
}
}
func Float64p(key string, value *float64) Field {
return Field{
Key: key,
Value: Float64pValue(value),
}
}
func Complex64(key string, value complex64) Field {
return Field{
Key: key,
Value: Complex128Value(complex128(value)),
}
}
func Complex64s(key string, value ...complex64) Field {
return Field{
Key: key,
Value: Complex64sValue(value),
}
}
func Complex64p(key string, value *complex64) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Complex128(key string, value complex128) Field {
return Field{
Key: key,
Value: Complex128Value(value),
}
}
func Complex128s(key string, value ...complex128) Field {
return Field{
Key: key,
Value: Complex128sValue(value),
}
}
func Complex128p(key string, value *complex128) Field {
return Field{
Key: key,
Value: Complex128pValue(value),
}
}
func Uintptr(key string, value uintptr) Field {
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uintptrs(key string, value ...uintptr) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uintptrp(key string, value *uintptr) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Bytes(key string, value []byte) Field {
return Field{
Key: key,
Value: BytesValue(value),
}
}
func Duration(key string, value time.Duration) Field {
return Field{
Key: key,
Value: DurationValue(value),
}
}
func Durations(key string, value ...time.Duration) Field {
return Field{
Key: key,
Value: DurationsValue(value),
}
}
func Durationp(key string, value *time.Duration) Field {
return Field{
Key: key,
Value: DurationpValue(value),
}
}
func Time(key string, value time.Time) Field {
return Field{
Key: key,
Value: TimeValue(value),
}
}
func Times(key string, value ...time.Time) Field {
return Field{
Key: key,
Value: TimesValue(value),
}
}
func Timep(key string, value *time.Time) Field {
return Field{
Key: key,
Value: TimepValue(value),
}
}
func FormatTime(key, format string, value time.Time) Field {
return Field{
Key: key,
Value: ClosureValue(func() any {
return value.Format(format)
}),
}
}
func FormatTimes(key, format string, value ...time.Time) Field {
return Field{
Key: key,
Value: ClosureValue(func() any {
times := make([]any, len(value))
for idx, val := range value {
times[idx] = val.Format(format)
}
return times
}),
}
}
func FormatTimep(key, format string, value *time.Time) Field {
isNill := value == nil
return Field{
Key: key,
Value: ClosureValue(func() any {
if isNill {
return NilValue()
}
return value.Format(format)
}),
}
}
func Error(key string, value error) Field {
return Field{
Key: key,
Value: ErrorValue(value),
}
}
func Errors(key string, value ...error) Field {
return Field{
Key: key,
Value: ErrorsValue(value),
}
}
func Groups(key string, value ...Field) Field {
return Field{
Key: key,
Value: GroupValue(value...),
}
}
func Valuer(key string, value LogValuer) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func ValuerFn(key string, value ClosureFn) Field {
return Field{
Key: key,
Value: ClosureValue(value),
}
}
// Field struct.
type Field struct {
Key string
Value Value
}
// String implent stringer.
func (f Field) String() string {
return fmt.Sprintf("%s=%+v", f.Key, f.Value)
}

34
field/fields.go Normal file
View File

@@ -0,0 +1,34 @@
package field
type Fields []Field
func (f Fields) Fields(fn func(Field) bool) {
for idx := range f {
if !fn(f[idx]) {
return
}
}
}
func (f Fields) Any() any {
fields := make(map[string]any)
for idx := range f {
fields[f[idx].Key] = f[idx].Value.Any()
}
return fields
}
func (f Fields) Append(fields ...Field) Fields {
f = append(f, fields...)
return f
}
func (f Fields) Set(idx int, field Field) {
f[idx] = field
}
func (f Fields) Len() int {
return len(f)
}

18
field/fields_test.go Normal file
View File

@@ -0,0 +1,18 @@
package field_test
import (
"testing"
"gitoa.ru/go-4devs/log/field"
)
func TestFields_Append(t *testing.T) {
t.Parallel()
fields := field.Fields{field.Any("any", "value")}
fields = fields.Append(field.String("string", "value"))
if len(fields) != 2 {
t.Fatalf("require 2 field got %v", len(fields))
}
}

86
field/kind.go Normal file
View File

@@ -0,0 +1,86 @@
package field
import "fmt"
//go:generate stringer -type=Kind -linecomment -output=kind_string.go
type Kind int
const (
KindAny Kind = iota // any
KindArray // array
KindNil // nil
KindString // string
KindBool // bool
KindInt64 // int64
KindUint64 // uint64
KindFloat32 // float32
KindFloat64 // float64
KindComplex128 // complex128
KindBinary // bytes
KindDuration // duration
KindTime // time
KindError // error
KindGroup // group
KindClosure // closure
)
func (l Kind) MarshalJSON() ([]byte, error) {
return []byte("\"" + l.String() + "\""), nil
}
func (l *Kind) UnmarshalJSON(in []byte) error {
return l.UnmarshalText(in[1 : len(in)-1])
}
func (l Kind) MarshalText() ([]byte, error) {
return []byte(l.String()), nil
}
//nolint:gocyclo,cyclop
func (l *Kind) UnmarshalText(in []byte) error {
switch string(in) {
case KindAny.String():
*l = KindAny
case KindArray.String():
*l = KindArray
case KindNil.String():
*l = KindNil
case KindString.String():
*l = KindString
case KindBool.String():
*l = KindBool
case KindInt64.String():
*l = KindInt64
case KindUint64.String():
*l = KindUint64
case KindFloat32.String():
*l = KindFloat32
case KindFloat64.String():
*l = KindFloat64
case KindComplex128.String():
*l = KindComplex128
case KindBinary.String():
*l = KindBinary
case KindDuration.String():
*l = KindDuration
case KindTime.String():
*l = KindTime
case KindError.String():
*l = KindError
case KindGroup.String():
*l = KindGroup
case KindClosure.String():
*l = KindClosure
}
return fmt.Errorf("%w:filed(%v)", ErrUndefined, string(in))
}
func (l Kind) MarshalBinary() ([]byte, error) {
return []byte(l.String()), nil
}
func (l *Kind) UnmarshalBinary(in []byte) error {
return l.UnmarshalText(in)
}

38
field/kind_string.go Normal file
View File

@@ -0,0 +1,38 @@
// Code generated by "stringer -type=Kind -linecomment -output=kind_string.go"; DO NOT EDIT.
package field
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[KindAny-0]
_ = x[KindArray-1]
_ = x[KindNil-2]
_ = x[KindString-3]
_ = x[KindBool-4]
_ = x[KindInt64-5]
_ = x[KindUint64-6]
_ = x[KindFloat32-7]
_ = x[KindFloat64-8]
_ = x[KindComplex128-9]
_ = x[KindBinary-10]
_ = x[KindDuration-11]
_ = x[KindTime-12]
_ = x[KindError-13]
_ = x[KindGroup-14]
_ = x[KindClosure-15]
}
const _Kind_name = "anyarraynilstringboolint64uint64float32float64complex128bytesdurationtimeerrorgroupclosure"
var _Kind_index = [...]uint8{0, 3, 8, 11, 17, 21, 26, 32, 39, 46, 56, 61, 69, 73, 78, 83, 90}
func (i Kind) String() string {
if i < 0 || i >= Kind(len(_Kind_index)-1) {
return "Kind(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Kind_name[_Kind_index[i]:_Kind_index[i+1]]
}

11
field/log_valuer.go Normal file
View File

@@ -0,0 +1,11 @@
package field
type LogValuer interface {
LogValue() any
}
type ClosureFn func() any
func (v ClosureFn) LogValue() any {
return v()
}

112
field/safe_set.go Normal file
View File

@@ -0,0 +1,112 @@
package field
import "unicode/utf8"
// Copied from encoding/json/tables.go.
//
// safeSet holds the value true if the ASCII character with the given array
// position can be represented inside a JSON string without any further
// escaping.
//
// All values are true except for the ASCII control characters (0-31), the
// double quote ("), and the backslash character ("\").
//
//nolint:gochecknoglobals
var safeSet = [utf8.RuneSelf]bool{
' ': true,
'!': true,
'"': false,
'#': true,
'$': true,
'%': true,
'&': true,
'\'': true,
'(': true,
')': true,
'*': true,
'+': true,
',': true,
'-': true,
'.': true,
'/': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
':': true,
';': true,
'<': true,
'=': true,
'>': true,
'?': true,
'@': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'V': true,
'W': true,
'X': true,
'Y': true,
'Z': true,
'[': true,
'\\': false,
']': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'{': true,
'|': true,
'}': true,
'~': true,
'\u007f': true,
}

651
field/value.go Normal file
View File

@@ -0,0 +1,651 @@
// nolint: exhaustruct
package field
import (
"fmt"
"math"
"strconv"
"time"
"unsafe"
)
// StringValue returns a new Value for a string.
func StringValue(value string) Value {
return Value{
num: uint64(len(value)),
any: stringptr(unsafe.StringData(value)),
Kind: KindString,
}
}
// StringpValue returns a new Value for a *string.
func StringpValue(value *string) Value {
if value == nil {
return NilValue()
}
return StringValue(*value)
}
// StringpValue returns a new Value for a string.
func StringsValue(value []string) Value {
return Value{
Kind: KindArray,
num: uint64(len(value)),
any: func() []Value {
values := make([]Value, len(value))
for idx := range value {
values[idx] = StringValue(value[idx])
}
return values
},
}
}
// BoolValue returns a Value for a bool.
func BoolValue(v bool) Value {
u := uint64(0)
if v {
u = 1
}
return Value{num: u, Kind: KindBool}
}
// BoolsValue returns a Value for a []bool.
func BoolsValue(values []bool) Value {
return Value{
Kind: KindArray,
num: uint64(len(values)),
any: func() []Value {
vals := make([]Value, len(values))
for idx := range values {
vals[idx] = BoolValue(values[idx])
}
return vals
},
}
}
// BoolpValue returns a new Value for a *bool.
func BoolpValue(value *bool) Value {
if value == nil {
return NilValue()
}
return BoolValue(*value)
}
// Uint64Value returns a Value for a uint64.
func Uint64Value(v uint64) Value {
return Value{num: v, Kind: KindUint64}
}
// Uint64sValue returns a Value for a []uint64.
func Uint64sValue(values []uint64) Value {
return Value{
Kind: KindArray,
num: uint64(len(values)),
any: func() []Value {
vals := make([]Value, len(values))
for idx := range values {
vals[idx] = Uint64Value(values[idx])
}
return vals
},
}
}
// Uint8sValue returns a Value for a []uint8.
func Uint8sValue(values []uint8) Value {
return Value{
Kind: KindArray,
num: uint64(len(values)),
any: func() []Value {
vals := make([]Value, len(values))
for idx := range values {
vals[idx] = Uint64Value(uint64(values[idx]))
}
return vals
},
}
}
// Uint64sValue returns a Value for a []uint64.
func Uint64pValue(v *uint64) Value {
if v == nil {
return NilValue()
}
return Uint64Value(*v)
}
// Int64Value returns a Value for an int64.
func Int64Value(value int64) Value {
return Value{inum: value, Kind: KindInt64}
}
// Int64sValue returns a Value for an []int64.
func Int64sValue(value []int64) Value {
return Value{
Kind: KindArray,
num: uint64(len(value)),
any: func() []Value {
vals := make([]Value, len(value))
for idx := range value {
vals[idx] = Int64Value(value[idx])
}
return vals
},
}
}
// Int64sValue returns a Value for an *int64.
func Int64pValue(value *int64) Value {
if value == nil {
return NilValue()
}
return Int64Value(*value)
}
// Float64Value returns a Value for a floating-point number.
func Float64Value(v float64) Value {
return Value{num: math.Float64bits(v), Kind: KindFloat64}
}
// Float64Value returns a Value for a floating-points number.
func Float64sValue(values []float64) Value {
return Value{
Kind: KindArray,
num: uint64(len(values)),
any: func() []Value {
vals := make([]Value, len(values))
for idx := range values {
vals[idx] = Float64Value(values[idx])
}
return vals
},
}
}
// Float64Value returns a Value for a floating-points number.
func Float64pValue(v *float64) Value {
if v == nil {
return NilValue()
}
return Float64Value(*v)
}
// Complex64sValue returns a Value for a []complex64.
func Complex64sValue(values []complex64) Value {
return Value{
Kind: KindArray,
num: uint64(len(values)),
any: func() []Value {
vals := make([]Value, len(values))
for idx := range values {
vals[idx] = Complex128Value(complex128(values[idx]))
}
return vals
},
}
}
// Complex128Value returns a Value for a complex128.
func Complex128Value(v complex128) Value {
return Value{
Kind: KindComplex128,
any: v,
}
}
// Complex128Value returns a Value for a []complex128.
func Complex128sValue(values []complex128) Value {
return Value{
Kind: KindArray,
num: uint64(len(values)),
any: func() []Value {
vals := make([]Value, len(values))
for idx := range values {
vals[idx] = Complex128Value(values[idx])
}
return vals
},
}
}
// Complex128Value returns a Value for a *complex128.
func Complex128pValue(v *complex128) Value {
if v == nil {
return NilValue()
}
return Complex128Value(*v)
}
// TimeValue returns a Value for a time.Time.
func TimeValue(v time.Time) Value {
return Value{inum: v.UnixNano(), any: v.Location(), Kind: KindTime}
}
// TimepValue returns a Value for a *time.Time.
func TimepValue(v *time.Time) Value {
if v == nil {
return NilValue()
}
return TimeValue(*v)
}
// TimesValue returns a Value for a []time.Time.
func TimesValue(values []time.Time) Value {
return Value{
Kind: KindArray,
num: uint64(len(values)),
any: func() []Value {
vals := make([]Value, len(values))
for idx := range values {
vals[idx] = TimeValue(values[idx])
}
return vals
},
}
}
func ClosureValue(fn ClosureFn) Value {
return Value{
Kind: KindClosure,
any: fn,
}
}
// DurationValue returns a Value for a time.Duration.
func DurationValue(v time.Duration) Value {
return Value{inum: v.Nanoseconds(), Kind: KindDuration}
}
// DurationValue returns a Value for a *time.Duration.
func DurationpValue(v *time.Duration) Value {
if v == nil {
return NilValue()
}
return DurationValue(*v)
}
// DurationValue returns a Value for a *time.Duration.
func DurationsValue(values []time.Duration) Value {
return Value{
Kind: KindArray,
num: uint64(len(values)),
any: func() []Value {
vals := make([]Value, len(values))
for idx := range values {
vals[idx] = DurationValue(values[idx])
}
return vals
},
}
}
// GroupValue returns a new Value for a list of Fields.
func GroupValue(as ...Field) Value {
return Value{
num: uint64(len(as)),
any: groupptr(unsafe.SliceData(as)),
Kind: KindGroup,
}
}
func ErrorValue(value error) Value {
return Value{
Kind: KindError,
any: value,
}
}
func ErrorsValue(value []error) Value {
return Value{
Kind: KindArray,
num: uint64(len(value)),
any: func() []Value {
vals := make([]Value, len(value))
for idx := range value {
vals[idx] = ErrorValue(value[idx])
}
return vals
},
}
}
func BytesValue(value []byte) Value {
return Value{
Kind: KindBinary,
any: value,
}
}
//nolint:gochecknoglobals
var nilValue = Value{
Kind: KindNil,
}
func NilValue() Value {
return nilValue
}
// AnyValue returns a Value for the supplied value.
//
//nolint:funlen,gocyclo,cyclop
func AnyValue(v any) Value {
switch value := v.(type) {
case string:
return StringValue(value)
case int:
return Int64Value(int64(value))
case uint:
return Uint64Value(uint64(value))
case int64:
return Int64Value(value)
case *int64:
return Int64pValue(value)
case []int64:
return Int64sValue(value)
case uint64:
return Uint64Value(value)
case *uint64:
return Uint64pValue(value)
case []uint64:
return Uint64sValue(value)
case bool:
return BoolValue(value)
case *bool:
return BoolpValue(value)
case []bool:
return BoolsValue(value)
case nil:
return NilValue()
case complex128:
return Complex128Value(value)
case *complex128:
return Complex128pValue(value)
case []complex128:
return Complex128sValue(value)
case complex64:
return Complex128Value(complex128(value))
case []complex64:
return Complex64sValue(value)
case time.Duration:
return DurationValue(value)
case *time.Duration:
return DurationpValue(value)
case []time.Duration:
return DurationsValue(value)
case time.Time:
return TimeValue(value)
case *time.Time:
return TimepValue(value)
case []time.Time:
return TimesValue(value)
case uint8:
return Uint64Value(uint64(value))
case []uint8:
return Uint8sValue(value)
case uint16:
return Uint64Value(uint64(value))
case uint32:
return Uint64Value(uint64(value))
case uintptr:
return Uint64Value(uint64(value))
case int8:
return Int64Value(int64(value))
case int16:
return Int64Value(int64(value))
case int32:
return Int64Value(int64(value))
case float64:
return Float64Value(value)
case *float64:
return Float64pValue(value)
case []float64:
return Float64sValue(value)
case float32:
return Float64Value(float64(value))
case error:
return ErrorValue(value)
case []error:
return ErrorsValue(value)
case []Field:
return GroupValue(value...)
case Fields:
return GroupValue(value...)
case Kind:
return Value{Kind: value}
case func() any:
return ClosureValue(value)
case ClosureFn:
return ClosureValue(value)
case LogValuer:
return ClosureValue(value.LogValue)
case Value:
return value
default:
return Value{any: value}
}
}
type (
stringptr *byte // used in Value.any when the Value is a string
groupptr *Field // used in Value.any when the Value is a []Field
)
type Value struct {
Kind Kind
num uint64
inum int64
any any
}
func (v Value) String() string {
if sp, ok := v.any.(stringptr); ok {
return unsafe.String(sp, v.num)
}
var buf []byte
return string(v.append(buf))
}
// append appends a text representation of v to dst.
// v is formatted as with fmt.Sprint.
//
//nolint:gomnd,cyclop
func (v Value) append(dst []byte) []byte {
switch v.Kind {
case KindString:
return append(dst, v.AsString()...)
case KindInt64:
return strconv.AppendInt(dst, v.inum, 10)
case KindUint64:
return strconv.AppendUint(dst, v.num, 10)
case KindFloat64:
return strconv.AppendFloat(dst, v.AsFloat64(), 'g', -1, 64)
case KindFloat32:
return strconv.AppendFloat(dst, float64(v.AsFloat32()), 'g', -1, 32)
case KindBool:
return strconv.AppendBool(dst, v.AsBool())
case KindDuration:
return append(dst, v.AsDuration().String()...)
case KindTime:
return append(dst, v.AsTime().String()...)
case KindError:
return append(dst, v.AsError().Error()...)
case KindGroup:
return fmt.Append(dst, v.AsGroup())
case KindClosure:
return fmt.Append(dst, v.Resolve())
case KindAny:
return fmt.Append(dst, v.any)
default:
return fmt.Appendf(dst, "%+v", v.any)
}
}
// nolint: gocyclo,cyclop
func (v Value) Any() any {
switch v.Kind {
case KindAny, KindBinary:
return v.any
case KindString:
return v.AsString()
case KindInt64:
return v.AsInt64()
case KindArray:
return v.AsArray().Resolve()
case KindBool:
return v.AsBool()
case KindClosure:
return v.Resolve()
case KindComplex128:
return v.AsComplex128()
case KindDuration:
return v.AsDuration()
case KindTime:
return v.AsTime()
case KindError:
return v.AsError()
case KindFloat32:
return v.AsFloat32()
case KindFloat64:
return v.AsFloat64()
case KindNil:
return nil
case KindUint64:
return v.AsUint64()
case KindGroup:
return v.AsGroup().Any()
}
return v.any
}
// nolint: forcetypeassert
func (v Value) AsString() string {
if v.Kind != KindString {
return ""
}
return unsafe.String(v.any.(stringptr), v.num)
}
func (v Value) AsBool() bool {
return v.num == 1
}
func (v Value) AsInt64() int64 {
return v.inum
}
func (v Value) AsUint() uint {
return uint(v.num)
}
func (v Value) AsUint64() uint64 {
return v.num
}
func (v Value) AsFloat32() float32 {
return math.Float32frombits(uint32(v.num))
}
func (v Value) AsFloat64() float64 {
return math.Float64frombits(v.num)
}
func (v Value) AsComplex128() complex128 {
cmplex, _ := v.any.(complex128)
return cmplex
}
func (v Value) AsUintptr() uintptr {
return uintptr(v.num)
}
func (v Value) AsBinary() []byte {
bytes, _ := v.any.([]byte)
return bytes
}
func (v Value) AsDuration() time.Duration {
return time.Duration(v.inum)
}
func (v Value) AsTime() time.Time {
loc, ok := v.any.(*time.Location)
if !ok {
return time.Time{}
}
return time.Unix(0, v.inum).In(loc)
}
func (v Value) AsError() error {
err, _ := v.any.(error)
return err
}
//nolint:forcetypeassert
func (v Value) AsGroup() Fields {
if v.Kind != KindGroup {
return nil
}
return unsafe.Slice((*Field)(v.any.(groupptr)), v.num)
}
func (v Value) Resolve() any {
cl, ok := v.any.(ClosureFn)
if !ok {
return nil
}
return cl()
}
type Values []Value
func (v Values) Resolve() any {
res := make([]any, len(v))
for idx := range v {
res[idx] = v[idx].Any()
}
return res
}
func (v Value) AsArray() Values {
switch res := v.any.(type) {
case []Value:
return res
case func() []Value:
return res()
default:
return nil
}
}

View File

@@ -2,18 +2,27 @@ package log
import (
"context"
"io"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
)
//nolint:gochecknoglobals
var global = With(New(), WithLevel(LevelDebug))
var global = With(New(),
WithSource(2),
WithLevel(KeyLevel, level.Debug),
WithExit(level.Alert),
WithPanic(level.Emergency),
)
// SetLogger sets global used logger. This function is not thread-safe.
func SetLogger(l Logger) {
global = l
}
// GetLogger return global logger.
func GetLogger() Logger {
// Log return global logger.
func Log() Logger {
return global
}
@@ -87,43 +96,83 @@ func Panicln(args ...interface{}) {
global.Panicln(args...)
}
// EmergKV log by emergency level and key-values.
func EmergKV(ctx context.Context, msg string, args ...interface{}) {
// EmergKVs sugared log by emergency level and key-values.
func EmergKVs(ctx context.Context, msg string, args ...interface{}) {
global.EmergKVs(ctx, msg, args...)
}
// AlertKVs sugared log by alert level and key-values.
func AlertKVs(ctx context.Context, msg string, args ...interface{}) {
global.AlertKVs(ctx, msg, args...)
}
// CritKVs sugared log by critcal level and key-values.
func CritKVs(ctx context.Context, msg string, args ...interface{}) {
global.CritKVs(ctx, msg, args...)
}
// ErrKVs sugared log by error level and key-values.
func ErrKVs(ctx context.Context, msg string, args ...interface{}) {
global.ErrKVs(ctx, msg, args...)
}
// WarnKVs sugared log by warning level and key-values.
func WarnKVs(ctx context.Context, msg string, args ...interface{}) {
global.WarnKVs(ctx, msg, args...)
}
// NoticeKVs sugared log by notice level and key-values.
func NoticeKVs(ctx context.Context, msg string, args ...interface{}) {
global.NoticeKVs(ctx, msg, args...)
}
// InfoKVs sugared log by info level and key-values.
func InfoKVs(ctx context.Context, msg string, args ...interface{}) {
global.InfoKVs(ctx, msg, args...)
}
// DebugKVs sugared log by debug level and key-values.
func DebugKVs(ctx context.Context, msg string, args ...interface{}) {
global.DebugKVs(ctx, msg, args...)
}
// EmergKV log by emergency level and key-values.
func EmergKV(ctx context.Context, msg string, args ...field.Field) {
global.EmergKV(ctx, msg, args...)
}
// AlertKV log by alert level and key-values.
func AlertKV(ctx context.Context, msg string, args ...interface{}) {
func AlertKV(ctx context.Context, msg string, args ...field.Field) {
global.AlertKV(ctx, msg, args...)
}
// CritKV log by critcal level and key-values.
func CritKV(ctx context.Context, msg string, args ...interface{}) {
func CritKV(ctx context.Context, msg string, args ...field.Field) {
global.CritKV(ctx, msg, args...)
}
// ErrKV log by error level and key-values.
func ErrKV(ctx context.Context, msg string, args ...interface{}) {
func ErrKV(ctx context.Context, msg string, args ...field.Field) {
global.ErrKV(ctx, msg, args...)
}
// WarnKV log by warning level and key-values.
func WarnKV(ctx context.Context, msg string, args ...interface{}) {
func WarnKV(ctx context.Context, msg string, args ...field.Field) {
global.WarnKV(ctx, msg, args...)
}
// NoticeKV log by notice level and key-values.
func NoticeKV(ctx context.Context, msg string, args ...interface{}) {
func NoticeKV(ctx context.Context, msg string, args ...field.Field) {
global.NoticeKV(ctx, msg, args...)
}
// InfoKV log by info level and key-values.
func InfoKV(ctx context.Context, msg string, args ...interface{}) {
func InfoKV(ctx context.Context, msg string, args ...field.Field) {
global.InfoKV(ctx, msg, args...)
}
// DebugKV log by debug level and key-values.
func DebugKV(ctx context.Context, msg string, args ...interface{}) {
func DebugKV(ctx context.Context, msg string, args ...field.Field) {
global.DebugKV(ctx, msg, args...)
}
@@ -181,3 +230,7 @@ func Fatalf(format string, args ...interface{}) {
func Panicf(format string, args ...interface{}) {
global.Panicf(format, args...)
}
func Writer(ctx context.Context, level level.Level) io.Writer {
return global.Writer(ctx, level)
}

24
global_example_test.go Normal file
View File

@@ -0,0 +1,24 @@
package log_test
import (
"context"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/level"
)
func ExampleDebug() {
logger := log.With(log.New(log.WithStdout()),
log.WithSource(2),
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:21 level=debug
}

2
go.mod
View File

@@ -1,3 +1,3 @@
module gitoa.ru/go-4devs/log
go 1.14
go 1.20

0
go.sum Normal file
View File

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

@@ -0,0 +1,10 @@
module gitoa.ru/go-4devs/log/handler/logrus
go 1.20
require (
github.com/sirupsen/logrus v1.9.3
gitoa.ru/go-4devs/log v0.5.1
)
require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect

17
handler/logrus/go.sum Normal file
View File

@@ -0,0 +1,17 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gitoa.ru/go-4devs/log v0.5.1 h1:rrIyjpUaw8AjDCf7ZuH0HgCRf370O3TV29yrU1xizWM=
gitoa.ru/go-4devs/log v0.5.1/go.mod h1:tREtjEH2cTHl0p3uCVcH9g5tlqtsVNI/tDQVfq53Ty4=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

47
handler/logrus/logger.go Normal file
View File

@@ -0,0 +1,47 @@
package logrus
import (
"context"
"github.com/sirupsen/logrus"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
)
// Standard create new standart logrus handler.
func Standard() log.Logger {
return New(logrus.StandardLogger())
}
// New create new logrus handler.
func New(log *logrus.Logger) log.Logger {
return func(ctx context.Context, data *entry.Entry) (int, error) {
lrgFields := make(logrus.Fields, data.Fields().Len())
data.Fields().Fields(func(f field.Field) bool {
lrgFields[f.Key] = f.Value.Any()
return true
})
entry := log.WithContext(ctx).WithFields(lrgFields)
switch data.Level() {
case level.Emergency:
entry.Panic(data.Message())
case level.Alert:
entry.Fatal(data.Message())
case level.Critical, level.Error:
entry.Error(data.Message())
case level.Warning:
entry.Warn(data.Message())
case level.Notice, level.Info:
entry.Info(data.Message())
case level.Debug:
entry.Debug(data.Message())
}
return 0, nil
}
}

View File

@@ -0,0 +1,30 @@
package logrus_test
import (
"context"
"io"
"os"
slogrus "github.com/sirupsen/logrus"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/handler/logrus"
)
func ExampleNew_logrusHandler() {
ctx := context.Background()
lgrs := slogrus.New()
lgrs.SetOutput(os.Stdout)
lgrs.SetFormatter(&slogrus.TextFormatter{
DisableTimestamp: true,
})
log := logrus.New(lgrs)
log.Err(ctx, "log logrus")
log.ErrKV(ctx, "log logrus kv", field.Int("int", 42))
log.ErrKVs(ctx, "log logrus kv sugar", "err", io.EOF)
// Output:
// level=error msg="log logrus"
// level=error msg="log logrus kv" int=42
// level=error msg="log logrus kv sugar" err=EOF
}

View File

@@ -0,0 +1,38 @@
package logrus_test
import (
"bytes"
"context"
"strings"
"testing"
lgr "github.com/sirupsen/logrus"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/handler/logrus"
"gitoa.ru/go-4devs/log/level"
)
func TestNew(t *testing.T) {
t.Parallel()
ctx := context.Background()
buf := &bytes.Buffer{}
lgrus := lgr.New()
lgrus.SetLevel(lgr.DebugLevel)
lgrus.SetOutput(buf)
lgrus.SetFormatter(&lgr.TextFormatter{
DisableTimestamp: true,
})
handler := logrus.New(lgrus)
expect := "level=info msg=\"handle logrus message\"\n"
if _, err := handler(ctx, entry.New(entry.WithLevel(level.Info), entry.WithMessage("handle logrus message"))); err != nil {
t.Error(err)
}
if !strings.HasSuffix(buf.String(), expect) {
t.Errorf("invalid suffix\n got: %s\nexpect:%s\n", buf.String(), expect)
}
}

17
handler/otel/go.mod Normal file
View File

@@ -0,0 +1,17 @@
module gitoa.ru/go-4devs/log/handler/otel
go 1.21.5
require (
gitoa.ru/go-4devs/log v0.5.1
go.opentelemetry.io/otel v1.21.0
go.opentelemetry.io/otel/sdk v1.21.0
go.opentelemetry.io/otel/trace v1.21.0
)
require (
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect
golang.org/x/sys v0.14.0 // indirect
)

27
handler/otel/go.sum Normal file
View File

@@ -0,0 +1,27 @@
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gitoa.ru/go-4devs/log v0.5.1 h1:rrIyjpUaw8AjDCf7ZuH0HgCRf370O3TV29yrU1xizWM=
gitoa.ru/go-4devs/log v0.5.1/go.mod h1:tREtjEH2cTHl0p3uCVcH9g5tlqtsVNI/tDQVfq53Ty4=
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

59
handler/otel/helpers.go Normal file
View File

@@ -0,0 +1,59 @@
package otel
import (
"context"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
const (
fieldSeverityNumber = "SeverityNumber"
fieldSeverityText = "SeverityText"
levelFields = 2
)
func levels(lvl level.Level) Level {
switch lvl {
case level.Emergency:
return levelError3
case level.Alert:
return levelFatal
case level.Critical:
return levelError2
case level.Error:
return levelError
case level.Warning:
return levelWarn
case level.Notice:
return levelInfo2
case level.Info:
return levelInfo
case level.Debug:
return levelDebug
}
return 0
}
func addEvent(ctx context.Context, data *entry.Entry) {
span := trace.SpanFromContext(ctx)
attrs := make([]attribute.KeyValue, 0, data.Fields().Len()+levelFields)
lvl := levels(data.Level())
attrs = append(attrs,
attribute.String(fieldSeverityText, lvl.String()),
attribute.Int(fieldSeverityNumber, int(lvl)),
)
data.Fields().Fields(func(f field.Field) bool {
attrs = append(attrs, attribute.String(f.Key, f.Value.String()))
return true
})
span.AddEvent(data.Message(), trace.WithAttributes(attrs...))
}

16
handler/otel/level.go Normal file
View File

@@ -0,0 +1,16 @@
package otel
//go:generate stringer -type=Level -linecomment -output=level_string.go
type Level int
const (
levelDebug Level = 5 // DEBUG
levelInfo Level = 9 // INFO
levelInfo2 Level = 10 // INFO2
levelWarn Level = 13 // WARN
levelError Level = 17 // ERROR
levelError2 Level = 18 // ERROR2
levelError3 Level = 19 // ERROR3
levelFatal Level = 21 // FATAL
)

View File

@@ -0,0 +1,51 @@
// Code generated by "stringer -type=Level -linecomment -output=level_string.go"; DO NOT EDIT.
package otel
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[levelDebug-5]
_ = x[levelInfo-9]
_ = x[levelInfo2-10]
_ = x[levelWarn-13]
_ = x[levelError-17]
_ = x[levelError2-18]
_ = x[levelError3-19]
_ = x[levelFatal-21]
}
const (
_Level_name_0 = "DEBUG"
_Level_name_1 = "INFOINFO2"
_Level_name_2 = "WARN"
_Level_name_3 = "ERRORERROR2ERROR3"
_Level_name_4 = "FATAL"
)
var (
_Level_index_1 = [...]uint8{0, 4, 9}
_Level_index_3 = [...]uint8{0, 5, 11, 17}
)
func (i Level) String() string {
switch {
case i == 5:
return _Level_name_0
case 9 <= i && i <= 10:
i -= 9
return _Level_name_1[_Level_index_1[i]:_Level_index_1[i+1]]
case i == 13:
return _Level_name_2
case 17 <= i && i <= 19:
i -= 17
return _Level_name_3[_Level_index_3[i]:_Level_index_3[i+1]]
case i == 21:
return _Level_name_4
default:
return "Level(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

16
handler/otel/logger.go Normal file
View File

@@ -0,0 +1,16 @@
package otel
import (
"context"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/entry"
)
func New() log.Logger {
return func(ctx context.Context, e *entry.Entry) (int, error) {
addEvent(ctx, e)
return 0, nil
}
}

View File

@@ -0,0 +1,62 @@
package otel_test
import (
"context"
"fmt"
"io"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/handler/otel"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)
func ExampleNew_withTrace() {
ctx := context.Background()
logger := log.New(log.WithStdout()).With(otel.Middleware())
sctx, span := startSpan(ctx)
logger.Err(sctx, "log logrus")
logger.ErrKV(sctx, "log logrus kv", field.Int("int", 42))
logger.ErrKVs(sctx, "log logrus kv sugar", "err", io.EOF)
span.End()
// Output:
// msg="log logrus"
// msg="log logrus kv" int=42
// msg="log logrus kv sugar" err=EOF
// event: log logrus, SeverityText = ERROR, SeverityNumber = 17
// event: log logrus kv, SeverityText = ERROR, SeverityNumber = 17, int = 42
// event: log logrus kv sugar, SeverityText = ERROR, SeverityNumber = 17, err = EOF
}
func startSpan(ctx context.Context) (context.Context, trace.Span) {
tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter{}))
return tp.Tracer("logger").Start(ctx, "operation")
}
type exporter struct{}
func (e exporter) Shutdown(_ context.Context) error {
return nil
}
func (e exporter) ExportSpans(_ context.Context, spanData []sdktrace.ReadOnlySpan) error {
for _, data := range spanData {
for _, events := range data.Events() {
fmt.Print("event: ", events.Name)
for _, attr := range events.Attributes {
fmt.Printf(", %v = %v", attr.Key, attr.Value.AsInterface())
}
fmt.Print("\n")
}
}
return nil
}

View File

@@ -0,0 +1,16 @@
package otel
import (
"context"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/entry"
)
func Middleware() log.Middleware {
return func(ctx context.Context, e *entry.Entry, handler log.Logger) (int, error) {
addEvent(ctx, e)
return handler(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=

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

@@ -0,0 +1,66 @@
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"
)
func Nop() log.Logger {
return New(zap.NewNop())
}
func Example(options ...zap.Option) log.Logger {
return New(zap.NewExample(options...))
}
func Production(options ...zap.Option) log.Logger {
z, err := zap.NewProduction(options...)
if err != nil {
panic(err)
}
return New(z)
}
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)
}
}

42
internal/buffer/buffer.go Normal file
View File

@@ -0,0 +1,42 @@
package buffer
import "sync"
const bufferSize = 1024
type Buffer []byte
// Having an initial size gives a dramatic speedup.
//
//nolint:gochecknoglobals
var bufPool = sync.Pool{
New: func() any {
b := make([]byte, 0, bufferSize)
return (*Buffer)(&b)
},
}
//nolint:forcetypeassert
func New() *Buffer {
return bufPool.Get().(*Buffer)
}
func (b *Buffer) Free() {
// To reduce peak allocation, return only smaller buffers to the pool.
const maxBufferSize = 16 << 10
if cap(*b) <= maxBufferSize {
*b = (*b)[:0]
bufPool.Put(b)
}
}
func (b *Buffer) WriteString(s string) (int, error) {
*b = append(*b, s...)
return len(s), nil
}
func (b *Buffer) String() string {
return string(*b)
}

View File

@@ -1,22 +0,0 @@
package log
//go:generate stringer -type=Level -linecomment
// Level log.
type Level uint8
// awailable log levels.
const (
LevelEmergency Level = iota // emergency
LevelAlert // alert
LevelCritical // critical
LevelError // error
LevelWarning // warning
LevelNotice // notice
LevelInfo // info
LevelDebug // debug
)
func (l Level) MarshalJSON() ([]byte, error) {
return []byte("\"" + l.String() + "\""), nil
}

95
level/level.go Normal file
View File

@@ -0,0 +1,95 @@
package level
import (
"encoding"
"encoding/json"
"strings"
)
//go:generate stringer -type=Level -linecomment
var (
_ json.Marshaler = Level(0)
_ json.Unmarshaler = (*Level)(nil)
_ encoding.TextMarshaler = Level(0)
_ encoding.TextUnmarshaler = (*Level)(nil)
_ encoding.BinaryMarshaler = Level(0)
_ encoding.BinaryUnmarshaler = (*Level)(nil)
)
// Level log.
type Level uint32
// available log levels.
const (
Emergency Level = iota // emerg
Alert // alert
Critical // crit
Error // error
Warning // warning
Notice // notice
Info // info
Debug // debug
)
func (l Level) Is(level Level) bool {
return level == l
}
func (l Level) Enabled(level Level) bool {
return l <= level
}
func (l Level) MarshalJSON() ([]byte, error) {
return []byte("\"" + l.String() + "\""), nil
}
func (l *Level) UnmarshalJSON(in []byte) error {
lvl := Parse(string(in[1 : len(in)-1]))
*l = lvl
return nil
}
func (l Level) MarshalText() ([]byte, error) {
return []byte(l.String()), nil
}
func (l *Level) UnmarshalText(in []byte) error {
lvl := Parse(string(in))
*l = lvl
return nil
}
func (l Level) MarshalBinary() ([]byte, error) {
return []byte(l.String()), nil
}
func (l *Level) UnmarshalBinary(in []byte) error {
lvl := Parse(string(in))
*l = lvl
return nil
}
func Parse(lvl string) Level {
switch strings.ToLower(lvl) {
case "debug", "Debug", "DEBUG":
return Debug
case "info", "Info", "INFO":
return Info
case "notice", "Notice", "NOTICE":
return Notice
case "warning", "Warning", "WARNING", "warm", "Warm", "WARN":
return Warning
case "error", "Error", "ERROR", "err", "Err", "ERR":
return Error
case "critical", "Critical", "CRITICAL", "crit", "Crit", "CRIT":
return Critical
case "alert", "Alert", "ALERT":
return Alert
default:
return Emergency
}
}

View File

@@ -1,6 +1,6 @@
// Code generated by "stringer -type=Level -linecomment"; DO NOT EDIT.
package log
package level
import "strconv"
@@ -8,19 +8,19 @@ func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[LevelEmergency-0]
_ = x[LevelAlert-1]
_ = x[LevelCritical-2]
_ = x[LevelError-3]
_ = x[LevelWarning-4]
_ = x[LevelNotice-5]
_ = x[LevelInfo-6]
_ = x[LevelDebug-7]
_ = x[Emergency-0]
_ = x[Alert-1]
_ = x[Critical-2]
_ = x[Error-3]
_ = x[Warning-4]
_ = x[Notice-5]
_ = x[Info-6]
_ = x[Debug-7]
}
const _Level_name = "emergencyalertcriticalerrorwarningnoticeinfodebug"
const _Level_name = "emergalertcriterrorwarningnoticeinfodebug"
var _Level_index = [...]uint8{0, 9, 14, 22, 27, 34, 40, 44, 49}
var _Level_index = [...]uint8{0, 5, 10, 14, 19, 26, 32, 36, 41}
func (i Level) String() string {
if i >= Level(len(_Level_index)-1) {

65
level/level_test.go Normal file
View File

@@ -0,0 +1,65 @@
package level_test
import (
"testing"
"gitoa.ru/go-4devs/log/level"
)
func TestMarshalJSON(t *testing.T) {
t.Parallel()
levels := map[level.Level]string{
level.Emergency: `"emerg"`,
level.Alert: `"alert"`,
level.Critical: `"crit"`,
level.Error: `"error"`,
level.Warning: `"warning"`,
level.Notice: `"notice"`,
level.Info: `"info"`,
level.Debug: `"debug"`,
}
for level, expect := range levels {
actual, err := level.MarshalJSON()
if err != nil {
t.Errorf("%s got err: %s", level, err)
continue
}
if string(actual) != expect {
t.Errorf("%s got: %s expect: %s", level, actual, expect)
}
}
}
func TestUnmarshalJSON(t *testing.T) {
t.Parallel()
levels := map[level.Level][]string{
level.Emergency: {`"emerg"`, `"Emerg"`},
level.Alert: {`"alert"`, `"ALERT"`},
level.Critical: {`"crit"`, `"critical"`},
level.Error: {`"error"`, `"ERR"`},
level.Warning: {`"warning"`, `"Warning"`},
level.Notice: {`"notice"`},
level.Info: {`"info"`},
level.Debug: {`"debug"`, `"DEBUG"`},
}
for expect, actuals := range levels {
for _, actual := range actuals {
var level level.Level
if err := level.UnmarshalJSON([]byte(actual)); err != nil {
t.Errorf("%s got err: %s", level, err)
continue
}
if !level.Is(expect) {
t.Errorf("%s got: %s expect: %s", actual, level, expect)
}
}
}
}

242
logger.go
View File

@@ -3,53 +3,83 @@ package log
import (
"context"
"fmt"
"io"
"os"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
)
var _ io.Writer = (Logger)(nil)
const badKey = "!BADKEY"
func writeOutput(_ int, err error) {
if err != nil {
fmt.Fprint(os.Stderr, err)
}
}
// Logger logged message.
type Logger func(ctx context.Context, level Level, msg string, fields Fields)
type Logger func(ctx context.Context, entry *entry.Entry) (int, error)
func (l Logger) log(ctx context.Context, level Level, args ...interface{}) {
l(ctx, level, fmt.Sprint(args...), nil)
func (l Logger) Write(in []byte) (int, error) {
return l.write(context.Background(), level.Info, string(in))
}
func (l Logger) logKV(ctx context.Context, level Level, msg string, args ...interface{}) {
l(ctx, level, msg, l.kv(ctx, args...))
func (l Logger) write(ctx context.Context, level level.Level, msg string, fields ...field.Field) (int, error) {
data := entry.Get()
defer func() {
entry.Put(data)
}()
return l(ctx, data.SetLevel(level).SetMessage(msg).Add(fields...))
}
func (l Logger) logf(ctx context.Context, level Level, format string, args ...interface{}) {
l(ctx, level, fmt.Sprintf(format, args...), nil)
func (l Logger) writef(ctx context.Context, level level.Level, format string, args ...interface{}) (int, error) {
data := entry.Get()
defer func() {
entry.Put(data)
}()
return l(ctx, data.SetLevel(level).SetMessagef(format, args...))
}
func (l Logger) logln(ctx context.Context, level Level, args ...interface{}) {
l(ctx, level, fmt.Sprintln(args...), nil)
}
func (l Logger) kv(_ context.Context, args ...interface{}) field.Fields {
kvEntry := entry.Get()
func (l Logger) kv(ctx context.Context, args ...interface{}) []Field {
fields := make([]Field, 0, len(args))
defer func() {
entry.Put(kvEntry)
}()
for i := 0; i < len(args); i++ {
if f, ok := args[i].(Field); ok {
fields = append(fields, f)
if f, ok := args[i].(field.Field); ok {
kvEntry = kvEntry.Add(f)
continue
}
if i == len(args)-1 {
l(ctx, LevelCritical, fmt.Sprint("Ignored key without a value.", args[i]), fields)
kvEntry = kvEntry.AddAny(badKey, args[i])
break
}
i++
key, val := args[i-1], args[i]
key, val := args[i], args[i+1]
if keyStr, ok := key.(string); ok {
fields = append(fields, Field{Key: keyStr, Value: val})
kvEntry = kvEntry.AddAny(keyStr, val)
i++
continue
}
l(ctx, LevelCritical, fmt.Sprint("Ignored key-value pairs with non-string keys.", key, val), fields)
kvEntry = kvEntry.AddAny(badKey, args[i])
}
return fields
return kvEntry.Fields()
}
// With adds middlewares to logger.
@@ -59,165 +89,253 @@ func (l Logger) With(mw ...Middleware) Logger {
// Emerg log by emergency level.
func (l Logger) Emerg(ctx context.Context, args ...interface{}) {
l.log(ctx, LevelEmergency, args...)
writeOutput(l.writef(ctx, level.Emergency, "", args...))
}
// Alert log by alert level.
func (l Logger) Alert(ctx context.Context, args ...interface{}) {
l.log(ctx, LevelAlert, args...)
writeOutput(l.writef(ctx, level.Alert, "", args...))
}
// Crit log by critical level.
func (l Logger) Crit(ctx context.Context, args ...interface{}) {
l.log(ctx, LevelCritical, args...)
writeOutput(l.writef(ctx, level.Critical, "", args...))
}
// Err log by error level.
func (l Logger) Err(ctx context.Context, args ...interface{}) {
l.log(ctx, LevelError, args...)
writeOutput(l.writef(ctx, level.Error, "", args...))
}
// Warn log by warning level.
func (l Logger) Warn(ctx context.Context, args ...interface{}) {
l.log(ctx, LevelWarning, args...)
writeOutput(l.writef(ctx, level.Warning, "", args...))
}
// Notice log by notice level.
func (l Logger) Notice(ctx context.Context, args ...interface{}) {
l.log(ctx, LevelNotice, args...)
writeOutput(l.writef(ctx, level.Notice, "", args...))
}
// Info log by info level.
func (l Logger) Info(ctx context.Context, args ...interface{}) {
l.log(ctx, LevelInfo, args...)
writeOutput(l.writef(ctx, level.Info, "", args...))
}
// Debug log by debug level.
func (l Logger) Debug(ctx context.Context, args ...interface{}) {
l.log(ctx, LevelDebug, args...)
writeOutput(l.writef(ctx, level.Debug, "", args...))
}
// Print log by info level and arguments.
func (l Logger) Print(args ...interface{}) {
l.log(context.Background(), LevelInfo, args...)
writeOutput(l.writef(context.Background(), level.Info, "", args...))
}
// Fatal log by alert level and arguments.
func (l Logger) Fatal(args ...interface{}) {
l.log(context.Background(), LevelAlert, args...)
writeOutput(l.writef(context.Background(), level.Alert, "", args...))
}
// Panic log by emergency level and arguments.
func (l Logger) Panic(args ...interface{}) {
l.log(context.Background(), LevelEmergency, args...)
writeOutput(l.writef(context.Background(), level.Emergency, "", args...))
}
// Println log by info level and arguments.
func (l Logger) Println(args ...interface{}) {
l.logln(context.Background(), LevelInfo, args...)
writeOutput(l.write(context.Background(), level.Info, fmt.Sprintln(args...)))
}
// Fatalln log by alert level and arguments.
func (l Logger) Fatalln(args ...interface{}) {
l.logln(context.Background(), LevelAlert, args...)
writeOutput(l.write(context.Background(), level.Alert, fmt.Sprintln(args...)))
}
// Panicln log by emergency level and arguments.
func (l Logger) Panicln(args ...interface{}) {
l.logln(context.Background(), LevelEmergency, args...)
writeOutput(l.write(context.Background(), level.Emergency, fmt.Sprintln(args...)))
}
// EmergKVs sugared log by emergency level and key-values.
func (l Logger) EmergKVs(ctx context.Context, msg string, args ...interface{}) {
writeOutput(l.write(ctx, level.Emergency, msg, l.kv(ctx, args...)...))
}
// AlertKVs sugared log by alert level and key-values.
func (l Logger) AlertKVs(ctx context.Context, msg string, args ...interface{}) {
writeOutput(l.write(ctx, level.Alert, msg, l.kv(ctx, args...)...))
}
// CritKVs sugared log by critcal level and key-values.
func (l Logger) CritKVs(ctx context.Context, msg string, args ...interface{}) {
writeOutput(l.write(ctx, level.Critical, msg, l.kv(ctx, args...)...))
}
// ErrKVs sugared log by error level and key-values.
func (l Logger) ErrKVs(ctx context.Context, msg string, args ...interface{}) {
writeOutput(l.write(ctx, level.Error, msg, l.kv(ctx, args...)...))
}
// WarnKVs sugared log by warning level and key-values.
func (l Logger) WarnKVs(ctx context.Context, msg string, args ...interface{}) {
writeOutput(l.write(ctx, level.Warning, msg, l.kv(ctx, args...)...))
}
// NoticeKVs sugared log by notice level and key-values.
func (l Logger) NoticeKVs(ctx context.Context, msg string, args ...interface{}) {
writeOutput(l.write(ctx, level.Notice, msg, l.kv(ctx, args...)...))
}
// InfoKVs sugared log by info level and key-values.
func (l Logger) InfoKVs(ctx context.Context, msg string, args ...interface{}) {
writeOutput(l.write(ctx, level.Info, msg, l.kv(ctx, args...)...))
}
// DebugKVs sugared log by debug level and key-values.
func (l Logger) DebugKVs(ctx context.Context, msg string, args ...interface{}) {
writeOutput(l.write(ctx, level.Debug, msg, l.kv(ctx, args...)...))
}
// EmergKV log by emergency level and key-values.
func (l Logger) EmergKV(ctx context.Context, msg string, args ...interface{}) {
l.logKV(ctx, LevelEmergency, msg, args...)
func (l Logger) EmergKV(ctx context.Context, msg string, args ...field.Field) {
writeOutput(l.write(ctx, level.Emergency, msg, args...))
}
// AlertKV log by alert level and key-values.
func (l Logger) AlertKV(ctx context.Context, msg string, args ...interface{}) {
l.logKV(ctx, LevelAlert, msg, args...)
func (l Logger) AlertKV(ctx context.Context, msg string, args ...field.Field) {
writeOutput(l.write(ctx, level.Alert, msg, args...))
}
// CritKV log by critcal level and key-values.
func (l Logger) CritKV(ctx context.Context, msg string, args ...interface{}) {
l.logKV(ctx, LevelCritical, msg, args...)
func (l Logger) CritKV(ctx context.Context, msg string, args ...field.Field) {
writeOutput(l.write(ctx, level.Critical, msg, args...))
}
// ErrKV log by error level and key-values.
func (l Logger) ErrKV(ctx context.Context, msg string, args ...interface{}) {
l.logKV(ctx, LevelError, msg, args...)
func (l Logger) ErrKV(ctx context.Context, msg string, args ...field.Field) {
writeOutput(l.write(ctx, level.Error, msg, args...))
}
// WarnKV log by warning level and key-values.
func (l Logger) WarnKV(ctx context.Context, msg string, args ...interface{}) {
l.logKV(ctx, LevelWarning, msg, args...)
func (l Logger) WarnKV(ctx context.Context, msg string, args ...field.Field) {
writeOutput(l.write(ctx, level.Warning, msg, args...))
}
// NoticeKV log by notice level and key-values.
func (l Logger) NoticeKV(ctx context.Context, msg string, args ...interface{}) {
l.logKV(ctx, LevelNotice, msg, args...)
func (l Logger) NoticeKV(ctx context.Context, msg string, args ...field.Field) {
writeOutput(l.write(ctx, level.Notice, msg, args...))
}
// InfoKV log by info level and key-values.
func (l Logger) InfoKV(ctx context.Context, msg string, args ...interface{}) {
l.logKV(ctx, LevelInfo, msg, args...)
func (l Logger) InfoKV(ctx context.Context, msg string, args ...field.Field) {
writeOutput(l.write(ctx, level.Info, msg, args...))
}
// DebugKV log by debug level and key-values.
func (l Logger) DebugKV(ctx context.Context, msg string, args ...interface{}) {
l.logKV(ctx, LevelDebug, msg, args...)
func (l Logger) DebugKV(ctx context.Context, msg string, args ...field.Field) {
writeOutput(l.write(ctx, level.Debug, msg, args...))
}
// Emergf log by emergency level by format and arguments.
func (l Logger) Emergf(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, LevelEmergency, format, args...)
writeOutput(l.writef(ctx, level.Emergency, format, args...))
}
// Alertf log by alert level by format and arguments.
func (l Logger) Alertf(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, LevelAlert, format, args...)
writeOutput(l.writef(ctx, level.Alert, format, args...))
}
// Critf log by critical level by format and arguments.
func (l Logger) Critf(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, LevelCritical, format, args...)
writeOutput(l.writef(ctx, level.Critical, format, args...))
}
// Errf log by error level by format and arguments.
func (l Logger) Errf(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, LevelError, format, args...)
writeOutput(l.writef(ctx, level.Error, format, args...))
}
// Warnf log by warning level by format and arguments.
func (l Logger) Warnf(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, LevelWarning, format, args...)
writeOutput(l.writef(ctx, level.Warning, format, args...))
}
// Noticef log by notice level by format and arguments.
func (l Logger) Noticef(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, LevelNotice, format, args...)
writeOutput(l.writef(ctx, level.Notice, format, args...))
}
// Infof log by info level by format and arguments.
func (l Logger) Infof(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, LevelInfo, format, args...)
writeOutput(l.writef(ctx, level.Info, format, args...))
}
// Debugf log by debug level by format and arguments.
func (l Logger) Debugf(ctx context.Context, format string, args ...interface{}) {
l.logf(ctx, LevelDebug, format, args...)
writeOutput(l.writef(ctx, level.Debug, format, args...))
}
// Printf log by info level by format and arguments without context.
func (l Logger) Printf(format string, args ...interface{}) {
l.logf(context.Background(), LevelInfo, format, args...)
writeOutput(l.writef(context.Background(), level.Info, format, args...))
}
// Fatalf log by alert level by format and arguments without context.
func (l Logger) Fatalf(format string, args ...interface{}) {
l.logf(context.Background(), LevelAlert, format, args...)
writeOutput(l.writef(context.Background(), level.Alert, format, args...))
}
// Panicf log by emergency level and arguments without context.
func (l Logger) Panicf(format string, args ...interface{}) {
l.logf(context.Background(), LevelEmergency, format, args...)
writeOutput(l.writef(context.Background(), level.Emergency, format, args...))
}
func (l Logger) Writer(ctx context.Context, level level.Level, fields ...field.Field) io.Writer {
return writer{
ctx: ctx,
level: level,
Logger: l,
fields: fields,
}
}
//nolint:containedctx
type writer struct {
ctx context.Context
level level.Level
fields []field.Field
Logger
}
func (w writer) WithLevel(level level.Level) writer {
return writer{
level: level,
Logger: w.Logger,
ctx: w.ctx,
fields: w.fields,
}
}
func (w writer) WithContext(ctx context.Context) writer {
return writer{
level: w.level,
Logger: w.Logger,
ctx: ctx,
fields: w.fields,
}
}
func (w writer) WithFields(fields ...field.Field) writer {
return writer{
level: w.level,
Logger: w.Logger,
ctx: w.ctx,
fields: fields,
}
}
func (w writer) Write(in []byte) (int, error) {
return w.write(w.ctx, w.level, string(in), w.fields...)
}

View File

@@ -0,0 +1,22 @@
package log_test
import (
"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.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
}

View File

@@ -2,16 +2,26 @@ package log_test
import (
"context"
"errors"
"fmt"
std "log"
"math"
"os"
"sync/atomic"
"time"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
)
//nolint:gochecknoglobals
var ctx = context.Background()
func setStdout() {
// set stout for example by default stderror
log.SetLogger(log.New(log.WithStdout()).With(log.WithLevel(log.KeyLevel, level.Debug)))
}
func ExampleNew() {
logger := log.New(log.WithStdout())
logger.Info(ctx, "same message")
@@ -19,17 +29,15 @@ func ExampleNew() {
}
func ExampleInfo() {
std.SetOutput(os.Stdout)
std.SetFlags(0)
setStdout()
log.Info(ctx, "same message")
// Output: msg="same message" level=info
}
func ExampleErrKV() {
std.SetOutput(os.Stdout)
std.SetFlags(0)
log.ErrKV(ctx, "same message", "key", "addition value")
// Output: msg="same message" key=addition value level=error
setStdout()
log.ErrKVs(ctx, "same message", "key", "addition value")
// Output: msg="same message" key="addition value" level=error
}
func ExampleNew_errf() {
@@ -39,52 +47,188 @@ func ExampleNew_errf() {
}
func ExampleNew_debugKV() {
logger := log.New(log.WithStdout()).With(log.WithLevel(log.LevelDebug))
logger.DebugKV(ctx, "same message", "error", os.ErrNotExist)
// Output: msg="same message" error=file does not exist level=debug
logger := log.New(log.WithStdout()).With(log.WithLevel("level", 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(log.LevelError))
logger.Info(ctx, "same message")
// Output:
logger := log.New(log.WithStdout()).With(log.WithLevel("level", 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.Info(ctx, "same message")
// Output:
}
type Obj struct {
Name string
IsEnable bool
}
var (
obj = Obj{
Name: "test obj",
}
str = "test str"
boolsVal = true
intVal = int(math.MaxInt)
int8Val = int8(math.MaxInt8)
int16Val = int16(math.MaxInt16)
int32Val = int32(math.MaxInt32)
int64Val = int64(math.MaxInt64)
uintVal = uint(math.MaxUint)
uint8Val = uint8(math.MaxUint8)
uint16Val = uint16(math.MaxInt16)
uint32Val = uint32(math.MaxInt32)
uint64Val = uint64(math.MaxInt64)
float32Val = float32(math.MaxFloat32)
float64Val = float64(math.MaxFloat64)
minute = time.Minute
timeVal = time.Unix(0, math.MaxInt32).In(time.UTC)
)
func ExampleNew_anyField() {
logger := log.New(log.WithStdout(), log.WithJSONFormat())
logger.InfoKV(ctx, "any info message",
field.Any("obj", Obj{Name: "obj name"}),
field.Any("obj", &obj),
field.Any("int", intVal),
field.Any("uint", uintVal),
field.Any("float", float64Val),
field.Any("time", timeVal),
field.Any("duration", time.Hour),
field.Any("error", errors.New("error")),
)
// Output:
// {"msg":"any info message","obj":{"Name":"obj name","IsEnable":false},"obj":{"Name":"test obj","IsEnable":false},"int":9223372036854775807,"uint":18446744073709551615,"float":1.7976931348623157e+308,"time":"1970-01-01T00:00:02Z","duration":"1h0m0s","error":"error"}
}
func ExampleNew_arrayField() {
logger := log.New(log.WithStdout(), log.WithJSONFormat())
logger.InfoKV(ctx, "array info message",
field.Strings("strings", "string", str),
field.Bools("bools", true, false),
field.Ints("ints", 42, 24),
field.Int8s("int8s", 42, 24),
field.Int16s("int16s", 42, 24),
field.Int32s("int32s", 42, 24),
field.Int64s("int64s", 42, 24),
field.Uint8s("uint8s", uint8Val, 0),
field.Uint16s("uint16s", 42, 24),
field.Uint32s("uint32s", 42, 24),
field.Uint64s("uint64s", 42, 24),
field.Float32s("float32s", 42, 24),
field.Float64s("float64s", 42, 24),
field.Complex64s("complex64s", 42, 24),
field.Complex128s("complex128s", 42, 24),
field.Durations("durations", time.Minute, time.Second),
field.Times("times", time.Unix(0, 42).In(time.UTC), time.Unix(0, 24).In(time.UTC)),
field.Errors("errors", errors.New("error"), errors.New("error2")),
)
// Output:
// {"msg":"array info message","strings":["string","test str"],"bools":[true,false],"ints":[42,24],"int8s":[42,24],"int16s":[42,24],"int32s":[42,24],"int64s":[42,24],"uint8s":[255,0],"uint16s":[42,24],"uint32s":[42,24],"uint64s":[42,24],"float32s":[42,24],"float64s":[42,24],"complex64s":["(42+0i)","(24+0i)"],"complex128s":["(42+0i)","(24+0i)"],"durations":["1m0s","1s"],"times":["1970-01-01T00:00:00Z","1970-01-01T00:00:00Z"],"errors":["error","error2"]}
}
func ExampleNew_pointerField() {
logger := log.New(log.WithStdout(), log.WithJSONFormat())
logger.InfoKV(ctx, "pointer info message",
field.Stringp("stringp", &str),
field.Stringp("stringp", nil),
field.Boolp("boolp", &boolsVal),
field.Boolp("boolp", nil),
field.Intp("intp", &intVal),
field.Intp("intp", nil),
field.Int8p("int8p", &int8Val),
field.Int8p("int8p", nil),
field.Int16p("int16p", &int16Val),
field.Int16p("int16p", nil),
field.Int32p("int32p", &int32Val),
field.Int32p("int32p", nil),
field.Int64p("int64p", &int64Val),
field.Int64p("int64p", nil),
field.Uintp("uintp", &uintVal),
field.Uintp("uintp", nil),
field.Uint8p("uint8p", &uint8Val),
field.Uint8p("uint8p", nil),
field.Uint16p("uint16p", &uint16Val),
field.Uint16p("uint16p", nil),
field.Uint32p("uint32p", &uint32Val),
field.Uint32p("uint32p", nil),
field.Uint64p("uint64p", &uint64Val),
field.Uint64p("uint64p", nil),
field.Float32p("float32p", &float32Val),
field.Float32p("float32p", nil),
field.Float64p("float64p", &float64Val),
field.Float64p("float64p", nil),
field.Durationp("durationp", &minute),
field.Durationp("durationp", nil),
field.Timep("timep", &timeVal),
field.Timep("timep", nil),
)
// Output:
// {"msg":"pointer info message","stringp":"test str","stringp":null,"boolp":true,"boolp":null,"intp":9223372036854775807,"intp":null,"int8p":127,"int8p":null,"int16p":32767,"int16p":null,"int32p":2147483647,"int32p":null,"int64p":9223372036854775807,"int64p":null,"uintp":18446744073709551615,"uintp":null,"uint8p":255,"uint8p":null,"uint16p":32767,"uint16p":null,"uint32p":2147483647,"uint32p":null,"uint64p":9223372036854775807,"uint64p":null,"float32p":3.4028235e+38,"float32p":null,"float64p":1.7976931348623157e+308,"float64p":null,"durationp":"1m0s","durationp":null,"timep":"1970-01-01T00:00:02Z","timep":null}
}
func ExampleNew_fields() {
logger := log.New(log.WithStdout(), log.WithJSONFormat())
logger.InfoKV(ctx, "info message",
field.String("string", str),
field.Bool("bool", true),
field.Int("int", 42),
field.Int8("int8", 42),
field.Int16("int16", 42),
field.Int32("int32", 42),
field.Int64("int64", 42),
field.Uint8("uint8", uint8Val),
field.Uint16("uint16", 42),
field.Uint32("uint32", 42),
field.Uint64("uint64", 42),
field.Float32("float32", 42),
field.Float64("float64", 42),
field.Complex64("complex16", 42),
field.Complex128("complex128", 42),
field.Duration("duration", time.Minute),
field.Time("time", timeVal),
field.FormatTime("format_time", time.UnixDate, timeVal),
field.Error("error", errors.New("error")),
)
// Output:
// {"msg":"info message","string":"test str","bool":true,"int":42,"int8":42,"int16":42,"int32":42,"int64":42,"uint8":255,"uint16":42,"uint32":42,"uint64":42,"float32":42,"float64":42,"complex16":"(42+0i)","complex128":"(42+0i)","duration":"1m0s","time":"1970-01-01T00:00:02Z","format_time":"Thu Jan 1 00:00:02 UTC 1970","error":"error"}
}
func ExampleNew_jsonFormat() {
logger := log.New(log.WithStdout(), log.WithJSONFormat()).
With(
log.WithCaller(4, true),
log.WithLevel(log.LevelDebug),
log.WithLevel(log.KeyLevel, level.Debug),
log.GoVersion("go-version"),
)
logger.Err(ctx, "same error message")
// Output: {"caller":"logger_example_test.go:63","go-version":"go1.14.2","level":"error","msg":"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"}
}
func ExampleNew_withLogger() {
stdlogger := std.New(os.Stdout, "same prefix ", std.Lshortfile)
func ExampleNew_textEncoding() {
logger := log.With(
log.New(
log.WithLogger(
stdlogger,
func(msg string, fields log.Fields) string {
return fmt.Sprint("msg=\"", msg, "\" ", fields)
},
),
log.WithCalldepth(9),
),
log.WithLevel(log.LevelDebug),
log.New(log.WithStdout()),
log.WithLevel(log.KeyLevel, level.Debug),
log.GoVersion("go-version"),
)
logger.Err(ctx, "same error message")
logger.InfoKV(ctx, "same info message", "api-version", 0.1)
logger.InfoKVs(ctx, "same info message", "api-version", 0.1, "obj", Obj{Name: "text value", IsEnable: true})
// Output:
// same prefix logger_example_test.go:82: msg="same error message" level=error go-version=go1.14.2
// same prefix logger_example_test.go:83: msg="same info message" api-version=0.1 level=info go-version=go1.14.2
// 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
}
type ctxKey string
@@ -93,8 +237,8 @@ func (c ctxKey) String() string {
return string(c)
}
func levelInfo(ctx context.Context, level log.Level, msg string, fields log.Fields, handler log.Logger) {
handler(ctx, level, msg, append(fields, log.Field{Key: "level", Value: level}))
func levelInfo(ctx context.Context, entry *entry.Entry, handler log.Logger) (int, error) {
return handler(ctx, entry.Add(field.String("level", entry.Level().String())))
}
func ExampleWith() {
@@ -106,7 +250,7 @@ func ExampleWith() {
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.14.2
// Output: msg="same message" level=info requestID=6a5fa048-7181-11ea-bc55-0242ac130003 api=0.1.0 go=go1.21.5
}
func ExampleLogger_Print() {
@@ -115,12 +259,52 @@ func ExampleLogger_Print() {
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.14.2
// Output: msg="same message" level=info client=http api=0.1.0 go=go1.21.5
}
func ExamplePrint() {
std.SetOutput(os.Stdout)
std.SetFlags(0)
setStdout()
log.Print("same message")
// Output: msg="same message" level=info
}
func Example_fieldClosureFn() {
cnt := int32(0)
closure := field.ClosureFn(func() any {
d := fmt.Sprintf("additional error data: %d", cnt)
atomic.AddInt32(&cnt, 1)
return d
})
log := log.With(log.New(log.WithStdout()), log.WithLevel("level", level.Info))
log.DebugKVs(ctx, "debug message", "data", closure)
log.ErrKVs(ctx, "error message", "err", closure)
log.WarnKVs(ctx, "warn message", "warn", closure)
// Output:
// msg="error message" err="additional error data: 0" level=error
// msg="warn message" warn="additional error data: 1" level=warning
}
func Example_withGroup() {
log := log.With(log.New(log.WithStdout()), log.WithLevel(log.KeyLevel, level.Info))
log.ErrKVs(ctx, "error message",
field.Groups("grous_field",
field.Error("err", os.ErrDeadlineExceeded),
field.Bool("bool", false),
),
)
log.WarnKV(ctx, "error message", field.ValuerFn("valuer_field", func() any {
return field.Fields{
field.Int("int_value", math.MaxInt),
field.Uint8("uint8_value", math.MaxUint8),
}
}))
// Output:
// msg="error message" grous_field.err="i/o timeout" grous_field.bool=false level=error
// msg="error message" valuer_field.int_value=9223372036854775807 valuer_field.uint8_value=255 level=warning
}

View File

@@ -4,28 +4,112 @@ import (
"bytes"
"context"
"os"
"sync/atomic"
"testing"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
)
var requestID ctxKey = "requestID"
func TestFields(t *testing.T) {
t.Parallel()
type rObj struct {
id string
}
var cnt int32
ctx := context.Background()
buf := &bytes.Buffer{}
log := log.New(log.WithWriter(buf))
success := "msg=\"message\" err=file already exists version=0.1.0 obj={id:uid}\n"
log := log.New(log.WithWriter(buf)).
With(log.WithLevel("level", level.Info))
success := "msg=message err=\"file already exists\" version=0.1.0 obj={id:uid} closure=\"some closure data\" level=info\n"
log.InfoKV(ctx, "message",
log.InfoKVs(ctx, "message",
"err", os.ErrExist,
"version", "0.1.0",
"obj", rObj{id: "uid"},
"closure", func() any {
atomic.AddInt32(&cnt, 1)
return "some closure data"
},
)
log.DebugKVs(ctx, "debug message",
"closure", field.ClosureFn(func() any {
atomic.AddInt32(&cnt, 1)
return "some debug data"
}),
)
if success != buf.String() {
t.Errorf("invalid value\n got:%s\n exp:%s", buf, success)
}
if cnt != 1 {
t.Errorf("invalid cnt value\n got:%d\n exp:1", cnt)
}
}
func TestWriter(t *testing.T) {
t.Parallel()
ctx := context.Background()
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.Writer(
context.WithValue(ctx, requestID, "6a5fa048-7181-11ea-bc55-0242ac1311113"),
level.Info,
field.Error("err", os.ErrExist),
).Write([]byte("info message"))
if success != buf.String() {
t.Errorf("invalid value\n got:%s\n exp:%s", buf, success)
}
buf.Reset()
_, _ = logger.Writer(ctx, level.Debug).Write([]byte("debug message"))
if buf.String() != "" {
t.Errorf("invalid value\n got:%s\n exp:%s", buf, success)
}
}
func TestLogger(t *testing.T) {
t.Parallel()
ctx := context.Background()
buf := &bytes.Buffer{}
logger := log.New(log.WithWriter(buf)).With(log.WithContextValue(requestID), log.WithLevel("level", level.Info))
_, err := logger(ctx, nil)
if err != nil {
t.Fatalf("expected <nil> err, got: %v", err)
}
if buf.String() != "" {
t.Errorf("invalid value\n got:%+v\n exp:\"\"", buf)
}
_, err = logger(ctx, entry.New().SetLevel(level.Error))
if err != nil {
t.Fatalf("expected <nil> err, got: %v", err)
}
success := "msg=\"\" requestID=<nil> level=error\n"
if buf.String() != success {
t.Errorf("invalid value\n got:%+v\n exp:%+v", buf, success)
}
}

View File

@@ -3,13 +3,17 @@ package log
import (
"context"
"fmt"
"path/filepath"
"os"
"runtime"
"time"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/level"
)
// Middleware handle.
type Middleware func(ctx context.Context, level Level, msg string, fields Fields, handler Logger)
type Middleware func(ctx context.Context, e *entry.Entry, handler Logger) (int, error)
// With add middleware to logger.
func With(logger Logger, mw ...Middleware) Logger {
@@ -17,95 +21,118 @@ func With(logger Logger, mw ...Middleware) Logger {
case 0:
return logger
case 1:
return func(ctx context.Context, level Level, msg string, fields Fields) {
mw[0](ctx, level, msg, fields, logger)
return func(ctx context.Context, entry *entry.Entry) (int, error) {
return mw[0](ctx, entry, logger)
}
}
lastI := len(mw) - 1
return func(ctx context.Context, level Level, msg string, fields Fields) {
return func(ctx context.Context, data *entry.Entry) (int, error) {
var (
chainHandler func(ctx context.Context, level Level, msg string, fields Fields)
chainHandler func(context.Context, *entry.Entry) (int, error)
curI int
)
chainHandler = func(currentCtx context.Context, currentLevel Level, currentMsg string, currentFields Fields) {
chainHandler = func(currentCtx context.Context, currentEntry *entry.Entry) (int, error) {
if curI == lastI {
logger(currentCtx, currentLevel, currentMsg, currentFields)
return
return logger(currentCtx, currentEntry)
}
curI++
mw[curI](currentCtx, currentLevel, currentMsg, currentFields, chainHandler)
n, err := mw[curI](currentCtx, currentEntry, chainHandler)
curI--
return n, err
}
mw[0](ctx, level, msg, fields, chainHandler)
return mw[0](ctx, data, chainHandler)
}
}
// WithLevel sets log level.
func WithLevel(lvl Level) Middleware {
return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) {
if level <= lvl {
handler(ctx, level, msg, append(fields, Field{Key: "level", Value: level}))
func WithLevel(key string, lvl level.Level) Middleware {
return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
if e.Level().Enabled(lvl) {
return handler(ctx, e.AddString(key, e.Level().String()))
}
return 0, nil
}
}
// KeyValue add field by const key value.
func KeyValue(key string, value interface{}) Middleware {
return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) {
handler(ctx, level, msg, append(fields, Field{Key: key, Value: value}))
return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
return handler(ctx, e.AddAny(key, value))
}
}
// GoVersion add field by go version.
func GoVersion(key string) Middleware {
return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) {
handler(ctx, level, msg, append(fields, Field{Key: key, Value: runtime.Version()}))
return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
return handler(ctx, e.AddString(key, runtime.Version()))
}
}
// WithContext add field by context key.
func WithContextValue(keys ...fmt.Stringer) Middleware {
return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) {
ctxFields := make(Fields, len(keys))
for i, key := range keys {
ctxFields[i] = Field{Key: key.String(), Value: ctx.Value(key)}
return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
for _, key := range keys {
e = e.AddAny(key.String(), ctx.Value(key))
}
handler(ctx, level, msg, append(fields, ctxFields...))
return handler(ctx, e)
}
}
// WithCaller adds called file.
func WithCaller(calldepth int, short bool) Middleware {
return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) {
_, file, line, ok := runtime.Caller(calldepth)
if !ok {
file, line = "???", 0
}
// Deprecated: use WithSource.
func WithCaller(key string, depth int, full bool) Middleware {
const offset = 2
if short && ok {
file = filepath.Base(file)
}
handler(ctx, level, msg, append(fields, NewField("caller", fmt.Sprint(file, ":", line))))
return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
return handler(ctx, e.AddString(key, entry.Caller(depth*offset, full)))
}
}
// WithTime adds time.
func WithTime(format string) Middleware {
return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) {
handler(ctx, level, msg, append(fields, NewField("time", time.Now().Format(format))))
func WithTime(key, format string) Middleware {
return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
return handler(ctx, e.Add(field.FormatTime(key, format, time.Now())))
}
}
// WithMetrics adds handle metrics.
func WithMetrics(metrics func(level Level)) Middleware {
return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) {
go metrics(level)
handler(ctx, level, msg, fields)
func WithMetrics(metrics func(level level.Level)) Middleware {
return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
go metrics(e.Level())
return handler(ctx, e)
}
}
// WithExit exit by level.
func WithExit(level level.Level) Middleware {
return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
n, err := handler(ctx, e)
if e.Level().Is(level) {
os.Exit(1)
}
return n, err
}
}
// WithPanic panic by level.
func WithPanic(level level.Level) Middleware {
return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
n, err := handler(ctx, e)
if e.Level().Is(level) {
panic(e.String())
}
return n, err
}
}

45
source.go Normal file
View File

@@ -0,0 +1,45 @@
package log
import (
"context"
"fmt"
"path/filepath"
"runtime"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
)
func WithSource(depth int) Middleware {
const offset = 3
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()))
}
fnc := runtime.FuncForPC(pc)
return handler(ctx, data.AddAny(KeySource, Source{
Func: filepath.Base(fnc.Name()),
File: filepath.Base(file),
Line: line,
}))
}
}
// 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
}

25
source_example_test.go Normal file
View File

@@ -0,0 +1,25 @@
package log_test
import (
"context"
"gitoa.ru/go-4devs/log"
)
func ExampleWithSource() {
ctx := context.Background()
logger := log.New(log.WithStdout()).With(log.WithSource(1))
logger.Debug(ctx, "debug message")
// Output:
// msg="debug message" source=source_example_test.go:13
}
func ExampleWithSource_json() {
ctx := context.Background()
logger := log.New(log.WithStdout(), log.WithJSONFormat()).With(log.WithSource(1))
logger.Debug(ctx, "debug message")
// Output:
// {"msg":"debug message","source":{"file":"source_example_test.go","line":22,"func":"log_test.ExampleWithSource_json"}}
}

121
std.go
View File

@@ -1,121 +0,0 @@
package log
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"os"
)
const (
calldepth = 3
)
// New creates standart logger.
func New(opts ...Option) Logger {
logger := logger{
format: stringFormat,
output: log.Output,
calldepth: calldepth,
}
for _, opt := range opts {
opt(&logger)
}
return func(ctx context.Context, level Level, msg string, fields Fields) {
_ = logger.output(logger.calldepth, logger.format(msg, fields))
switch level {
case LevelEmergency:
panic(msg)
case LevelAlert:
os.Exit(1)
default:
}
}
}
// Option configure log.
type Option func(*logger)
// Format sets formats output message.
type Format func(msg string, fields Fields) string
type logger struct {
output func(calldepth int, s string) error
format Format
calldepth int
}
// WithWriter sets writer logger.
func WithWriter(writer io.Writer) Option {
return func(l *logger) {
l.output = log.New(writer, "", 0).Output
}
}
// WithStdout sets logged to os.Stdout.
func WithStdout() Option {
return func(l *logger) {
l.output = log.New(os.Stdout, "", 0).Output
}
}
// WithFormat sets format log.
func WithFormat(format Format) Option {
return func(l *logger) {
l.format = format
}
}
// WithStringFormat sets format as simple string.
func WithStringFormat() Option {
return func(l *logger) {
l.format = stringFormat
}
}
// WithJSONFormat sets json output format.
func WithJSONFormat() Option {
return func(l *logger) {
l.format = jsonFormat
}
}
// WithCalldepth sets depth filename.
func WithCalldepth(calldepth int) Option {
return func(l *logger) {
l.calldepth = calldepth
}
}
// WithLogger sets logger anf format.
func WithLogger(std *log.Logger, format Format) Option {
return func(l *logger) {
l.output = std.Output
l.format = format
}
}
func stringFormat(msg string, fields Fields) string {
return fmt.Sprint("msg=\"", msg, "\" ", fields)
}
func jsonFormat(msg string, fields Fields) string {
data := make(map[string]interface{}, len(fields)+1)
data["msg"] = msg
for _, field := range fields {
data[field.Key] = field.Value
}
res, err := json.Marshal(data)
if err != nil {
return stringFormat(msg, append(fields, FieldError(err)))
}
return string(res)
}

129
writter.go Normal file
View File

@@ -0,0 +1,129 @@
package log
import (
"context"
"fmt"
"io"
"os"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/internal/buffer"
)
// Keys for "built-in" attributes.
const (
// TimeKey is the key used by the built-in handlers for the time
// when the log method is called. The associated Value is a [time.Time].
KeyTime = "time"
// LevelKey is the key used by the built-in handlers for the level
// of the log call. The associated value is a [Level].
KeyLevel = "level"
// MessageKey is the key used by the built-in handlers for the
// message of the log call. The associated value is a string.
KeyMessage = "msg"
// 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"
)
func WithWriter(w io.Writer) func(*option) {
return func(o *option) {
o.out = w
}
}
func WithStdout() func(*option) {
return func(o *option) {
o.out = os.Stdout
}
}
// WithStringFormat sets format as simple string.
func WithStringFormat() func(*option) {
return func(o *option) {
o.format = formatText()
}
}
// WithJSONFormat sets json output format.
func WithJSONFormat() func(*option) {
return func(o *option) {
o.format = formatJSON()
}
}
type option struct {
format func(io.Writer, *entry.Entry) (int, error)
out io.Writer
}
// New creates standart logger.
func New(opts ...func(*option)) Logger {
log := option{
format: formatText(),
out: os.Stderr,
}
for _, opt := range opts {
opt(&log)
}
return func(_ context.Context, entry *entry.Entry) (int, error) {
return log.format(log.out, entry)
}
}
func formatText() func(io.Writer, *entry.Entry) (int, error) {
enc := field.NewEncoderText()
return func(w io.Writer, entry *entry.Entry) (int, error) {
buf := buffer.New()
defer func() {
buf.Free()
}()
*buf = enc.AppendField(*buf, field.String(KeyMessage, entry.Message()))
for _, field := range entry.Fields() {
*buf = enc.AppendField(*buf, field)
}
_, _ = buf.WriteString("\n")
n, err := w.Write(*buf)
if err != nil {
return 0, fmt.Errorf("format text:%w", err)
}
return n, nil
}
}
func formatJSON() func(w io.Writer, entry *entry.Entry) (int, error) {
enc := field.NewEncoderJSON()
return func(w io.Writer, entry *entry.Entry) (int, error) {
buf := buffer.New()
defer func() {
buf.Free()
}()
_, _ = buf.WriteString("{")
*buf = enc.AppendField(*buf, field.String(KeyMessage, entry.Message()))
for _, field := range entry.Fields() {
*buf = enc.AppendField(*buf, field)
}
_, _ = buf.WriteString("}")
_, _ = buf.WriteString("\n")
n, err := w.Write(*buf)
if err != nil {
return 0, fmt.Errorf("format json:%w", err)
}
return n, nil
}
}