Browse Source

update field (#8)

Reviewed-on: https://gitoa.ru/go-4devs/log/pulls/8
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
pull/9/head v0.5.0
andrey 4 months ago
parent
commit
abbcf0b1a0
  1. 4
      .drone.yml
  2. 23
      .golangci.yml
  3. 1
      bench_test.go
  4. 303
      field/encoder.go
  5. 23
      field/encoder_json.go
  6. 38
      field/encoder_json_test.go
  7. 17
      field/encoder_text.go
  8. 5
      field/errors.go
  9. 448
      field/field.go
  10. 27
      field/fields.go
  11. 570
      field/key.go
  12. 86
      field/kind.go
  13. 38
      field/kind_string.go
  14. 11
      field/log_valuer.go
  15. 112
      field/safe_set.go
  16. 126
      field/type.go
  17. 1159
      field/value.go
  18. 4
      global.go
  19. 2
      go.mod
  20. 9
      handler/logrus/logger.go
  21. 9
      handler/otel/helpers.go
  22. 11
      handler/zap/logger.go
  23. 42
      internal/buffer/buffer.go
  24. 8
      level/level_test.go
  25. 17
      logger.go
  26. 202
      logger_example_test.go
  27. 2
      logger_example_trace_test.go
  28. 13
      logger_test.go
  29. 16
      middleware.go
  30. 162
      writter.go

4
.drone.yml

@ -3,7 +3,7 @@ name: default
steps:
- name: test
image: golang:1.18.1
image: golang:1.21.5
volumes:
- name: deps
path: /go/src/mod
@ -11,7 +11,7 @@ steps:
- go test
- name: golangci-lint
image: golangci/golangci-lint:v1.49
image: golangci/golangci-lint:v1.55
commands:
- golangci-lint run

23
.golangci.yml

@ -23,8 +23,9 @@ linters-settings:
min-name-length: 2
ignore-names:
- err
- "n"
- n
- i
- w
tagliatelle:
case:
use-field-name: true
@ -38,16 +39,19 @@ linters-settings:
linters:
enable-all: true
disable:
- exhaustivestruct
- maligned
- deadcode
# deprecated
- interfacer
- golint
- structcheck
- varcheck
- nosnakecase
- golint
- deadcode
- scopelint
- exhaustivestruct
- ifshort
- structcheck
- nosnakecase
- maligned
- depguard # need configure
issues:
# Excluding configuration per-path, per-linter, per-text and per-source
@ -57,3 +61,8 @@ issues:
- gomnd
- ireturn
- exhaustruct
- gochecknoglobals
- path: _example_test\.go
linters:
- lll
- goerr113

1
bench_test.go

@ -12,7 +12,6 @@ import (
"gitoa.ru/go-4devs/log/field"
)
//nolint:gochecknoglobals
var (
errExample = errors.New("fail")
_messages = fakeMessages(1000)

303
field/encoder.go

@ -1,32 +1,281 @@
//nolint:gomnd
package field
import "time"
import (
"fmt"
"strconv"
"time"
"unicode"
"unicode/utf8"
)
//nolint:interfacebloat
type Encoder interface {
// Built-in types.
AddArray(key string, value Value)
AddAny(key string, value Value)
AddNil(key string)
AddBool(key string, value bool)
AddBinary(key string, value []byte)
AddInt(key string, value int)
AddInt8(key string, value int8)
AddInt16(key string, value int16)
AddInt32(key string, value int32)
AddInt64(key string, value int64)
AddUint(key string, value uint)
AddUint8(key string, value uint8)
AddUint16(key string, value uint16)
AddUint32(key string, value uint32)
AddUint64(key string, value uint64)
AddUintptr(key string, value uintptr)
AddTime(key string, value time.Time)
AddDuration(key string, value time.Duration)
AddFloat32(key string, value float32)
AddFloat64(key string, value float64)
AddComplex64(key string, value complex64)
AddComplex128(key string, value complex128)
AddString(key, value string)
AddError(key string, value error)
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

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

38
field/encoder_json_test.go

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

17
field/encoder_text.go

@ -0,0 +1,17 @@
package field
import (
"fmt"
)
func NewEncoderText(opts ...func(*BaseEncoder)) BaseEncoder {
opts = append([]func(*BaseEncoder){
WithGropuConfig(0, 0, ' '),
WithNullValue("<nil>"),
WithDefaultValue(func(dst []byte, _ Encoder, val Value) []byte {
return fmt.Appendf(dst, "%+v", val.Any())
}),
}, opts...)
return NewEncoder(opts...)
}

5
field/errors.go

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

448
field/field.go

@ -5,329 +5,499 @@ import (
"time"
)
func Any(key string, value interface{}) Field {
return Key(key).Any(value)
func Any(key string, value any) Field {
return Field{
Key: key,
Value: AnyValue(value),
}
}
func String(key, value string) Field {
return Key(key).String(value)
return Field{
Key: key,
Value: StringValue(value),
}
}
func Stringp(key string, value *string) Field {
return Key(key).Stringp(value)
return Field{
Key: key,
Value: StringpValue(value),
}
}
func Strings(key string, value ...string) Field {
return Key(key).Strings(value...)
return Field{
Key: key,
Value: StringsValue(value),
}
}
func Bool(key string, value bool) Field {
return Key(key).Bool(value)
return Field{
Key: key,
Value: BoolValue(value),
}
}
func Bools(key string, value ...bool) Field {
return Key(key).Bools(value...)
return Field{
Key: key,
Value: BoolsValue(value),
}
}
func Boolp(key string, value *bool) Field {
return Key(key).Boolp(value)
return Field{
Key: key,
Value: BoolpValue(value),
}
}
func Uint(key string, value uint) Field {
return Key(key).Uint(value)
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uints(key string, value ...uint) Field {
return Key(key).Uints(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uintp(key string, value *uint) Field {
return Key(key).Uintp(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint8(key string, value uint8) Field {
return Key(key).Uint8(value)
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uint8s(key string, value ...uint8) Field {
return Key(key).Uint8s(value...)
return Field{
Key: key,
Value: Uint8sValue(value),
}
}
func Uint8p(key string, value *uint8) Field {
return Key(key).Uint8p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint16(key string, value uint16) Field {
return Key(key).Uint16(value)
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uint16s(key string, value ...uint16) Field {
return Key(key).Uint16s(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint16p(key string, value *uint16) Field {
return Key(key).Uint16p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint32(key string, value uint32) Field {
return Key(key).Uint32(value)
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uint32s(key string, value ...uint32) Field {
return Key(key).Uint32s(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint32p(key string, value *uint32) Field {
return Key(key).Uint32p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uint64(key string, value uint64) Field {
return Key(key).Uint64(value)
return Field{
Key: key,
Value: Uint64Value(value),
}
}
func Uint64s(key string, value ...uint64) Field {
return Key(key).Uint64s(value...)
return Field{
Key: key,
Value: Uint64sValue(value),
}
}
func Uint64p(key string, value *uint64) Field {
return Key(key).Uint64p(value)
return Field{
Key: key,
Value: Uint64pValue(value),
}
}
func Int(key string, value int) Field {
return Key(key).Int(value)
return Field{
Key: key,
Value: Int64Value(int64(value)),
}
}
func Ints(key string, value ...int) Field {
return Key(key).Ints(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Intp(key string, value *int) Field {
return Key(key).Intp(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int8(key string, value int8) Field {
return Key(key).Int8(value)
return Field{
Key: key,
Value: Int64Value(int64(value)),
}
}
func Int8s(key string, value ...int8) Field {
return Key(key).Int8s(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int8p(key string, value *int8) Field {
return Key(key).Int8p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int16(key string, value int16) Field {
return Key(key).Int16(value)
return Field{
Key: key,
Value: Int64Value(int64(value)),
}
}
func Int16s(key string, value ...int16) Field {
return Key(key).Int16s(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int16p(key string, value *int16) Field {
return Key(key).Int16p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int32(key string, value int32) Field {
return Key(key).Int32(value)
return Field{
Key: key,
Value: Int64Value(int64(value)),
}
}
func Int32s(key string, value ...int32) Field {
return Key(key).Int32s(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int32p(key string, value *int32) Field {
return Key(key).Int32p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Int64(key string, value int64) Field {
return Key(key).Int64(value)
return Field{
Key: key,
Value: Int64Value(value),
}
}
func Int64s(key string, value ...int64) Field {
return Key(key).Int64s(value...)
return Field{
Key: key,
Value: Int64sValue(value),
}
}
func Int64p(key string, value *int64) Field {
return Key(key).Int64p(value)
return Field{
Key: key,
Value: Int64pValue(value),
}
}
func Float32(key string, value float32) Field {
return Key(key).Float32(value)
return Field{
Key: key,
Value: Float64Value(float64(value)),
}
}
func Float32s(key string, value ...float32) Field {
return Key(key).Float32s(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Float32p(key string, value *float32) Field {
return Key(key).Float32p(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Float64(key string, value float64) Field {
return Key(key).Float64(value)
return Field{
Key: key,
Value: Float64Value(value),
}
}
func Float64s(key string, value ...float64) Field {
return Key(key).Float64s(value...)
return Field{
Key: key,
Value: Float64sValue(value),
}
}
func Float64p(key string, value *float64) Field {
return Key(key).Float64p(value)
return Field{
Key: key,
Value: Float64pValue(value),
}
}
func Complex64(key string, value complex64) Field {
return Key(key).Complex64(value)
return Field{
Key: key,
Value: Complex128Value(complex128(value)),
}
}
func Complex64s(key string, value ...complex64) Field {
return Key(key).Complex64s(value...)
return Field{
Key: key,
Value: Complex64sValue(value),
}
}
func Complex64p(key string, value *complex64) Field {
return Key(key).Complex64p(value)
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 Key(key).Uintptr(value)
return Field{
Key: key,
Value: Uint64Value(uint64(value)),
}
}
func Uintptrs(key string, value ...uintptr) Field {
return Key(key).Uintptrs(value...)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Uintptrp(key string, value *uintptr) Field {
return Key(key).Uintptrp(value)
return Field{
Key: key,
Value: AnyValue(value),
}
}
func Bytes(key string, value []byte) Field {
return Key(key).Bytes(value)
return Field{
Key: key,
Value: BytesValue(value),
}
}
func Duration(key string, value time.Duration) Field {
return Key(key).Dureation(value)
return Field{
Key: key,
Value: DurationValue(value),
}
}
func Durations(key string, value ...time.Duration) Field {
return Key(key).Dureations(value)
return Field{
Key: key,
Value: DurationsValue(value),
}
}
func Durationp(key string, value *time.Duration) Field {
return Key(key).Dureationp(value)
return Field{
Key: key,
Value: DurationpValue(value),
}
}
func Time(key string, value time.Time) Field {
return Key(key).Time(value)
return Field{
Key: key,
Value: TimeValue(value),
}
}
func Times(key string, value ...time.Time) Field {
return Key(key).Times(value...)
return Field{
Key: key,
Value: TimesValue(value),
}
}
func Timep(key string, value *time.Time) Field {
return Key(key).Timep(value)
return Field{
Key: key,
Value: TimepValue(value),
}
}
func FormatTime(key, format string, value time.Time) Field {
return Key(key).FormatTime(format, value)
return Field{
Key: key,
Value: ClosureValue(func() any {
return value.Format(format)
}),
}
}
func FormatTimes(key, foramt string, value ...time.Time) Field {
return Key(key).FormatTimes(foramt, value...)
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, foramt string, value *time.Time) Field {
return Key(key).FormatTimep(foramt, value)
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 Key(key).Error(value)
return Field{
Key: key,
Value: ErrorValue(value),
}
}
func Errors(key string, value ...error) Field {
return Key(key).Errors(value...)
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 Key
value Value
}
//nolint:gocyclo,cyclop
func (f Field) AddTo(enc Encoder) {
key := string(f.key)
switch {
case f.value.IsArray():
enc.AddArray(key, f.value)
case f.value.IsNil():
enc.AddNil(key)
case f.value.IsBool():
enc.AddBool(key, f.value.asBool())
case f.value.IsBinary():
enc.AddBinary(key, f.value.asBinary())
case f.value.IsInt():
enc.AddInt(key, f.value.asInt())
case f.value.IsInt8():
enc.AddInt8(key, f.value.asInt8())
case f.value.IsInt16():
enc.AddInt16(key, f.value.asInt16())
case f.value.IsInt32():
enc.AddInt32(key, f.value.asInt32())
case f.value.IsInt64():
enc.AddInt64(key, f.value.asInt64())
case f.value.IsUint():
enc.AddUint(key, f.value.asUint())
case f.value.IsUint8():
enc.AddUint8(key, f.value.asUint8())
case f.value.IsUint16():
enc.AddUint16(key, f.value.asUint16())
case f.value.IsUint32():
enc.AddUint32(key, f.value.asUint32())
case f.value.IsUint64():
enc.AddUint64(key, f.value.asUint64())
case f.value.IsUintptr():
enc.AddUintptr(key, f.value.asUintptr())
case f.value.IsTime():
enc.AddTime(key, f.value.asTime())
case f.value.IsDuration():
enc.AddDuration(key, f.value.asDuration())
case f.value.IsFloat32():
enc.AddFloat32(key, f.value.asFloat32())
case f.value.IsFloat64():
enc.AddFloat64(key, f.value.asFloat64())
case f.value.IsComplex64():
enc.AddComplex64(key, f.value.asComplex64())
case f.value.IsComplex128():
enc.AddComplex128(key, f.value.asComplex128())
case f.value.IsString():
enc.AddString(key, f.value.asString())
case f.value.IsError():
enc.AddError(key, f.value.asError())
default:
enc.AddAny(key, f.value)
}
}
func (f Field) Type() Type {
return f.value.vtype
}
func (f Field) Key() Key {
return f.key
}
func (f Field) Value() Value {
return f.value
}
func (f Field) AsInterface() interface{} {
return f.value.AsInterface()
Key string
Value Value
}
// String implent stringer.
func (f Field) String() string {
return fmt.Sprintf("%s=%+v", f.key, f.value.AsInterface())
return fmt.Sprintf("%s=%+v", f.Key, f.Value)
}

27
field/fields.go

@ -2,7 +2,22 @@ package field
type Fields []Field
type MapField map[Key]Value
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...)
@ -17,13 +32,3 @@ func (f Fields) Set(idx int, field Field) {
func (f Fields) Len() int {
return len(f)
}
func (f Fields) AsMap() MapField {
fields := make(MapField, len(f))
for _, field := range f {
fields[field.Key()] = field.Value()
}
return fields
}

570
field/key.go

@ -1,570 +0,0 @@
package field
import (
"time"
)
type Key string
//nolint:funlen,cyclop,gocyclo
func (k Key) Any(value interface{}) Field {
switch val := value.(type) {
case string:
return k.String(val)
case *string:
return k.Stringp(val)
case []string:
return k.Strings(val...)
case bool:
return k.Bool(val)
case *bool:
return k.Boolp(val)
case []bool:
return k.Bools(val...)
case int8:
return k.Int8(val)
case []int8:
return k.Int8s(val...)
case *int8:
return k.Int8p(val)
case int16:
return k.Int16(val)
case []int16:
return k.Int16s(val...)
case *int16:
return k.Int16p(val)
case int32:
return k.Int32(val)
case []int32:
return k.Int32s(val...)
case *int32:
return k.Int32p(val)
case int64:
return k.Int64(val)
case []int64:
return k.Int64s(val...)
case *int64:
return k.Int64p(val)
case uint:
return k.Uint(val)
case []uint:
return k.Uints(val...)
case *uint:
return k.Uintp(val)
case uint8:
return k.Uint8(val)
case *uint8:
return k.Uint8p(val)
case uint16:
return k.Uint16(val)
case []uint16:
return k.Uint16s(val...)
case *uint16:
return k.Uint16p(val)
case uint32:
return k.Uint32(val)
case []uint32:
return k.Uint32s(val...)
case *uint32:
return k.Uint32p(val)
case uint64:
return k.Uint64(val)
case []uint64:
return k.Uint64s(val...)
case *uint64:
return k.Uint64p(val)
case float32:
return k.Float32(val)
case []float32:
return k.Float32s(val...)
case *float32:
return k.Float32p(val)
case float64:
return k.Float64(val)
case []float64:
return k.Float64s(val...)
case *float64:
return k.Float64p(val)
case complex64:
return k.Complex64(val)
case []complex64:
return k.Complex64s(val...)
case *complex64:
return k.Complex64p(val)
case uintptr:
return k.Uintptr(val)
case []uintptr:
return k.Uintptrs(val...)
case *uintptr:
return k.Uintptrp(val)
case []byte:
return k.Bytes(val)
case time.Duration:
return k.Dureation(val)
case []time.Duration:
return k.Dureations(val)
case *time.Duration:
return k.Dureationp(val)
case time.Time:
return k.Time(val)
case []time.Time:
return k.Times(val...)
case *time.Time:
return k.Timep(val)
case error:
return k.Error(val)
case []error:
return k.Errors(val...)
}
return Field{
key: k,
value: Value{
value: value,
vtype: TypeAny,
numeric: 0,
stringly: "",
},
}
}
func (k Key) String(value string) Field {
return Field{
key: k,
value: stringValue(value),
}
}
func (k Key) Strings(value ...string) Field {
return Field{
key: k,
value: stringsValue(value),
}
}
func (k Key) Stringp(value *string) Field {
return Field{
key: k,
value: stringpValue(value),
}
}
func (k Key) Bool(value bool) Field {
return Field{
key: k,
value: boolValue(value),
}
}
func (k Key) Bools(value ...bool) Field {
return Field{
key: k,
value: boolsValue(value),
}
}
func (k Key) Boolp(value *bool) Field {
return Field{
key: k,
value: boolpValue(value),
}
}
func (k Key) Int(value int) Field {
return Field{
key: k,
value: intValue(value),
}
}
func (k Key) Ints(value ...int) Field {
return Field{
key: k,
value: intsValue(value),
}
}
func (k Key) Intp(value *int) Field {
return Field{
key: k,
value: intpValue(value),
}
}
func (k Key) Int8(value int8) Field {
return Field{
key: k,
value: int8Value(value),
}
}
func (k Key) Int8s(value ...int8) Field {
return Field{
key: k,
value: int8sValue(value),
}
}
func (k Key) Int8p(value *int8) Field {
return Field{
key: k,
value: int8pValue(value),
}
}
func (k Key) Int16(value int16) Field {
return Field{
key: k,
value: int16Value(value),
}
}
func (k Key) Int16s(value ...int16) Field {
return Field{
key: k,
value: int16sValue(value),
}
}
func (k Key) Int16p(value *int16) Field {
return Field{
key: k,
value: int16pValue(value),
}
}
func (k Key) Int32(value int32) Field {
return Field{
key: k,
value: int32Value(value),
}
}
func (k Key) Int32s(value ...int32) Field {
return Field{
key: k,
value: int32sValue(value),
}
}
func (k Key) Int32p(value *int32) Field {
return Field{
key: k,
value: int32pValue(value),
}
}
func (k Key) Int64(value int64) Field {
return Field{
key: k,
value: int64Value(value),
}
}
func (k Key) Int64s(value ...int64) Field {
return Field{
key: k,
value: int64sValue(value),
}
}
func (k Key) Int64p(value *int64) Field {
return Field{
key: k,
value: int64pValue(value),
}
}
func (k Key) Uint(value uint) Field {
return Field{
key: k,
value: uintValue(value),
}
}
func (k Key) Uints(value ...uint) Field {
return Field{
key: k,
value: uintsValue(value),
}
}
func (k Key) Uintp(value *uint) Field {
return Field{
key: k,
value: uintpValue(value),
}
}
func (k Key) Uint8(value uint8) Field {
return Field{
key: k,
value: uint8Value(value),
}
}
func (k Key) Uint8s(value ...uint8) Field {
return Field{
key: k,
value: uint8sValue(value),
}
}
func (k Key) Uint8p(value *uint8) Field {
return Field{
key: k,
value: uint8pValue(value),
}
}
func (k Key) Uint16(value uint16) Field {
return Field{
key: k,
value: uint16Value(value),
}
}
func (k Key) Uint16s(value ...uint16) Field {
return Field{
key: k,
value: uint16sValue(value),
}
}
func (k Key) Uint16p(value *uint16) Field {
return Field{
key: k,
value: uint16pValue(value),
}
}
func (k Key) Uint32(value uint32) Field {
return Field{
key: k,
value: uint32Value(value),
}
}
func (k Key) Uint32s(value ...uint32) Field {
return Field{
key: k,
value: uint32sValue(value),
}
}
func (k Key) Uint32p(value *uint32) Field {
return Field{
key: k,
value: uint32pValue(value),
}
}
func (k Key) Uint64(value uint64) Field {
return Field{
key: k,
value: uint64Value(value),
}
}
func (k Key) Uint64s(value ...uint64) Field {
return Field{
key: k,
value: uint64sValue(value),
}
}
func (k Key) Uint64p(value *uint64) Field {
return Field{
key: k,
value: uint64pValue(value),
}
}
func (k Key) Float32(value float32) Field {
return Field{
key: k,
value: float32Value(value),
}
}
func (k Key) Float32s(value ...float32) Field {
return Field{
key: k,
value: float32sValue(value),
}
}
func (k Key) Float32p(value *float32) Field {
return Field{
key: k,
value: float32pValue(value),
}
}
func (k Key) Float64(value float64) Field {
return Field{
key: k,
value: float64Value(value),
}
}
func (k Key) Float64s(value ...float64) Field {
return Field{
key: k,
value: float64sValue(value),
}
}
func (k Key) Float64p(value *float64) Field {
return Field{
key: k,
value: float64pValue(value),
}
}
func (k Key) Complex64(value complex64) Field {
return Field{
key: k,
value: complex64Value(value),
}
}
func (k Key) Complex64s(value ...complex64) Field {
return Field{
key: k,
value: complex64sValue(value),
}
}
func (k Key) Complex64p(value *complex64) Field {
return Field{
key: k,
value: complex64pValue(value),
}
}
func (k Key) Complex128(value complex128) Field {
return Field{
key: k,
value: complex128Value(value),
}
}
func (k Key) Complex128s(value []complex128) Field {
return Field{
key: k,
value: complex128sValue(value),
}
}
func (k Key) Complex128p(value *complex128) Field {
return Field{
key: k,
value: complex128pValue(value),
}
}
func (k Key) Uintptr(value uintptr) Field {
return Field{
key: k,
value: uintptrValue(value),
}
}
func (k Key) Uintptrs(value ...uintptr) Field {
return Field{
key: k,
value: uintptrsValue(value),
}
}
func (k Key) Uintptrp(value *uintptr) Field {
return Field{
key: k,
value: uintptrpValue(value),
}
}
func (k Key) Bytes(value []byte) Field {
return Field{
key: k,
value: bytesValue(value),
}
}
func (k Key) Dureation(value time.Duration) Field {
return Field{
key: k,
value: durationValue(value),
}
}
func (k Key) Dureations(value []time.Duration) Field {
return Field{
key: k,
value: durationsValue(value),
}
}
func (k Key) Dureationp(value *time.Duration) Field {
return Field{
key: k,
value: durationpValue(value),
}
}
func (k Key) Time(value time.Time) Field {
return Field{
key: k,
value: timeValue(value),
}
}
func (k Key) Times(value ...time.Time) Field {
return Field{
key: k,
value: timesValue(value),
}
}
func (k Key) Timep(value *time.Time) Field {
return Field{
key: k,
value: timepValue(value),
}
}
func (k Key) FormatTime(format string, value time.Time) Field {
return Field{
key: k,
value: formatTimeValue(format, value),
}
}
func (k Key) FormatTimes(format string, value ...time.Time) Field {
return Field{
key: k,
value: formatTimesValue(format, value),
}
}
func (k Key) FormatTimep(format string, value *time.Time) Field {
return Field{
key: k,
value: formatTimepValue(format, value),
}
}
func (k Key) Error(value error) Field {
return Field{
key: k,
value: errorValue(value),
}
}
func (k Key) Errors(value ...error) Field {
return Field{
key: k,
value: errorsValue(value),
}
}

86
field/kind.go

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

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

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

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

126
field/type.go

@ -1,126 +0,0 @@
package field
type Type uint32
const (
TypeAny Type = 1 << iota // any
TypeArray // array
TypeNil // nil
TypeString // string
TypeBool // bool
TypeInt // int
TypeInt8 // int8
TypeInt16 // int16
TypeInt32 // int32
TypeInt64 // int64
TypeUint // uint
TypeUint8 // uint8
TypeUint16 // uint16
TypeUint32 // uint32
TypeUint64 // uint64
TypeFloat32 // float32
TypeFloat64 // float64
TypeComplex64 // complex64
TypeComplex128 // complex128
TypeUintptr // uintptr
TypeBinary // bytes
TypeDuration // duration
TypeTime // time
TypeError // error
)
func (t Type) IsAny() bool {
return t&TypeAny > 0
}
func (t Type) IsArray() bool {
return t&TypeArray > 0
}
func (t Type) IsNil() bool {
return t&TypeNil > 0
}
func (t Type) IsBool() bool {
return t&TypeBool > 0
}
func (t Type) IsString() bool {
return t&TypeString > 0
}
func (t Type) IsInt() bool {
return t&TypeInt > 0
}
func (t Type) IsInt8() bool {
return t&TypeInt8 > 0
}
func (t Type) IsInt16() bool {
return t&TypeInt16 > 0
}
func (t Type) IsInt32() bool {
return t&TypeInt32 > 0
}
func (t Type) IsInt64() bool {
return t&TypeInt64 > 0
}
func (t Type) IsUint() bool {
return t&TypeUint > 0
}
func (t Type) IsUint8() bool {
return t&TypeUint8 > 0
}
func (t Type) IsUint16() bool {
return t&TypeUint16 > 0
}
func (t Type) IsUint32() bool {
return t&TypeUint32 > 0
}
func (t Type) IsUint64() bool {
return t&TypeUint64 > 0
}
func (t Type) IsFloat32() bool {
return t&TypeFloat32 > 0
}
func (t Type) IsFloat64() bool {
return t&TypeFloat64 > 0
}
func (t Type) IsComplex64() bool {
return t&TypeComplex64 > 0
}
func (t Type) IsComplex128() bool {
return t&TypeComplex128 > 0
}
func (t Type) IsUintptr() bool {
return t&TypeUintptr > 0
}
func (t Type) IsBinary() bool {
return t&TypeBinary > 0
}
func (t Type) IsDuration() bool {
return t&TypeDuration > 0
}
func (t Type) IsTime() bool {
return t&TypeTime > 0
}
func (t Type) IsError() bool {
return t&TypeError > 0
}

1159
field/value.go

File diff suppressed because it is too large

4
global.go

@ -10,8 +10,8 @@ import (
//nolint:gochecknoglobals
var global = With(New(),
WithCaller("caller", 1, false),
WithLevel("level", level.Debug),
WithCaller(KeySource, 1, false),
WithLevel(KeyLevel, level.Debug),
WithExit(level.Alert),
WithPanic(level.Emergency),
)

2
go.mod

@ -1,6 +1,6 @@
module gitoa.ru/go-4devs/log
go 1.17
go 1.20
require (
github.com/sirupsen/logrus v1.8.1

9
handler/logrus/logger.go

@ -6,6 +6,7 @@ import (
"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"
)
@ -18,9 +19,11 @@ func Standard() log.Logger {
func New(log *logrus.Logger) log.Logger {
return func(ctx context.Context, data *entry.Entry) (int, error) {
lrgFields := make(logrus.Fields, data.Fields().Len())
for _, field := range data.Fields() {
lrgFields[string(field.Key())] = field.AsInterface()
}
data.Fields().Fields(func(f field.Field) bool {
lrgFields[f.Key] = f.Value.Any()
return true
})
entry := log.WithContext(ctx).WithFields(lrgFields)

9
handler/otel/helpers.go

@ -4,6 +4,7 @@ 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"
@ -48,9 +49,11 @@ func addEvent(ctx context.Context, data *entry.Entry) {
attribute.Int(fieldSeverityNumber, int(lvl)),
)
for _, field := range data.Fields() {
attrs = append(attrs, attribute.String(string(field.Key()), field.Value().String()))
}
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...))
}

11
handler/zap/logger.go

@ -5,6 +5,7 @@ import (
"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"
)
@ -38,10 +39,12 @@ func Development(options ...zap.Option) log.Logger {
// 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, data.Fields().Len())
for i, field := range data.Fields() {
zf[i] = zap.Any(string(field.Key()), field.AsInterface())
}
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:

42
internal/buffer/buffer.go

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

8
level/level_test.go

@ -7,6 +7,8 @@ import (
)
func TestMarshalJSON(t *testing.T) {
t.Parallel()
levels := map[level.Level]string{
level.Emergency: `"emerg"`,
level.Alert: `"alert"`,
@ -22,8 +24,10 @@ func TestMarshalJSON(t *testing.T) {
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)
}
@ -31,6 +35,8 @@ func TestMarshalJSON(t *testing.T) {
}
func TestUnmarshalJSON(t *testing.T) {
t.Parallel()
levels := map[level.Level][]string{
level.Emergency: {`"emerg"`, `"Emerg"`},
level.Alert: {`"alert"`, `"ALERT"`},
@ -47,13 +53,13 @@ func TestUnmarshalJSON(t *testing.T) {
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)
}
}
}
}

17
logger.go

@ -2,7 +2,6 @@ package log
import (
"context"
"errors"
"fmt"
"io"
"os"
@ -14,10 +13,7 @@ import (
var _ io.Writer = (Logger)(nil)
var (
ErrIgnoredKey = errors.New("ignored key without a value")
ErrNonStringKeys = errors.New("ignored key-value pairs with non-string keys")
)
const badKey = "!BADKEY"
func writeOutput(_ int, err error) {
if err != nil {
@ -52,7 +48,7 @@ func (l Logger) writef(ctx context.Context, level level.Level, format string, ar
return l(ctx, data.SetLevel(level).SetMessagef(format, args...))
}
func (l Logger) kv(ctx context.Context, args ...interface{}) field.Fields {
func (l Logger) kv(_ context.Context, args ...interface{}) field.Fields {
kvEntry := entry.Get()
defer func() {
@ -67,21 +63,20 @@ func (l Logger) kv(ctx context.Context, args ...interface{}) field.Fields {
}
if i == len(args)-1 {
writeOutput(l.write(ctx, level.Critical, fmt.Sprint("Ignored key without a value.", args[i]), kvEntry.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 {
kvEntry = kvEntry.AddAny(keyStr, val)
i++
continue
}
writeOutput(l.write(ctx, level.Critical, fmt.Sprint("Ignored key-value pairs with non-string keys.", key, val), kvEntry.Fields()...))
kvEntry = kvEntry.AddAny(badKey, args[i])
}
return kvEntry.Fields()

202
logger_example_test.go

@ -2,9 +2,12 @@ package log_test
import (
"context"
"errors"
"fmt"
"math"
"os"
"sync/atomic"
"time"
"gitoa.ru/go-4devs/log"
"gitoa.ru/go-4devs/log/entry"
@ -12,12 +15,11 @@ import (
"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("level", level.Debug)))
log.SetLogger(log.New(log.WithStdout()).With(log.WithLevel(log.KeyLevel, level.Debug)))
}
func ExampleNew() {
@ -35,7 +37,7 @@ func ExampleInfo() {
func ExampleErrKV() {
setStdout()
log.ErrKVs(ctx, "same message", "key", "addition value")
// Output: msg="same message" key=addition value level=error
// Output: msg="same message" key="addition value" level=error
}
func ExampleNew_errf() {
@ -47,7 +49,7 @@ func ExampleNew_errf() {
func ExampleNew_debugKV() {
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
// Output: msg="same message" error="file does not exist" level=debug
}
func ExampleNew_level() {
@ -62,28 +64,171 @@ func ExampleNew_level_info() {
// 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)
)
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-01T03:00:02+03:00","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), time.Unix(0, 24)),
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-01T03:00:00+03:00","1970-01-01T03:00:00+03:00"],"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-01T03:00:02+03:00","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", time.Unix(0, 42)),
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-01T03:00:00+03:00","format_time":"Thu Jan 1 03:00:02 MSK 1970","error":"error"}
}
func ExampleNew_jsonFormat() {
logger := log.New(log.WithStdout(), log.WithJSONFormat()).
With(
log.WithLevel("level", level.Debug),
log.WithLevel(log.KeyLevel, level.Debug),
log.GoVersion("go-version"),
)
logger.Err(ctx, "same error message")
// Output: {"go-version":"go1.18.1","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_textEncoding() {
logger := log.With(
log.New(log.WithStdout()),
log.WithLevel("level", level.Debug),
log.WithLevel(log.KeyLevel, level.Debug),
log.GoVersion("go-version"),
)
logger.Err(ctx, "same error message")
logger.InfoKVs(ctx, "same info message", "api-version", 0.1)
logger.InfoKVs(ctx, "same info message", "api-version", 0.1, "obj", Obj{Name: "text value", IsEnable: true})
// Output:
// msg="same error message" level=error go-version=go1.18.1
// msg="same info message" api-version=0.1 level=info go-version=go1.18.1
// 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
@ -105,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.18.1
// Output: msg="same message" level=info requestID=6a5fa048-7181-11ea-bc55-0242ac130003 api=0.1.0 go=go1.21.5
}
func ExampleLogger_Print() {
@ -114,7 +259,7 @@ 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.18.1
// Output: msg="same message" level=info client=http api=0.1.0 go=go1.21.5
}
func ExamplePrint() {
@ -123,22 +268,43 @@ func ExamplePrint() {
// Output: msg="same message" level=info
}
func ExampleWithClosure() {
func Example_fieldClosureFn() {
cnt := int32(0)
closure := func() string {
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.WithClosure)
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
// 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
}

2
logger_example_trace_test.go

@ -44,7 +44,7 @@ func (e exporter) Shutdown(_ context.Context) error {
return nil
}
func (e exporter) ExportSpans(ctx context.Context, spanData []sdktrace.ReadOnlySpan) error {
func (e exporter) ExportSpans(_ context.Context, spanData []sdktrace.ReadOnlySpan) error {
for _, data := range spanData {
for _, events := range data.Events() {
fmt.Print("event: ", events.Name)

13
logger_test.go

@ -13,7 +13,6 @@ import (
"gitoa.ru/go-4devs/log/level"
)
//nolint:gochecknoglobals
var requestID ctxKey = "requestID"
func TestFields(t *testing.T) {
@ -28,14 +27,14 @@ func TestFields(t *testing.T) {
ctx := context.Background()
buf := &bytes.Buffer{}
log := log.New(log.WithWriter(buf)).
With(log.WithLevel("level", level.Info), log.WithClosure)
success := "msg=\"message\" err=file already exists version=0.1.0 obj={id:uid} closure=some closure data level=info\n"
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.InfoKVs(ctx, "message",
"err", os.ErrExist,
"version", "0.1.0",
"obj", rObj{id: "uid"},
"closure", func() string {
"closure", func() any {
atomic.AddInt32(&cnt, 1)
return "some closure data"
@ -43,11 +42,11 @@ func TestFields(t *testing.T) {
)
log.DebugKVs(ctx, "debug message",
"closure", func() string {
"closure", field.ClosureFn(func() any {
atomic.AddInt32(&cnt, 1)
return "some debug data"
},
}),
)
if success != buf.String() {
@ -64,7 +63,7 @@ func TestWriter(t *testing.T) {
ctx := context.Background()
success := "msg=\"info message\" err=file already exists requestID=6a5fa048-7181-11ea-bc55-0242ac1311113 level=info\n"
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))

16
middleware.go

@ -12,8 +12,6 @@ import (
"gitoa.ru/go-4devs/log/level"
)
var _ Middleware = WithClosure
// Middleware handle.
type Middleware func(ctx context.Context, e *entry.Entry, handler Logger) (int, error)
@ -62,18 +60,6 @@ func WithLevel(key string, lvl level.Level) Middleware {
}
}
func WithClosure(ctx context.Context, data *entry.Entry, handler Logger) (int, error) {
for i, field := range data.Fields() {
if field.Type().IsAny() {
if f, ok := field.AsInterface().(func() string); ok {
data.Fields().Set(i, field.Key().String(f()))
}
}
}
return handler(ctx, data)
}
// KeyValue add field by const key value.
func KeyValue(key string, value interface{}) Middleware {
return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
@ -111,7 +97,7 @@ func WithCaller(key string, depth int, full bool) Middleware {
// WithTime adds time.
func WithTime(key, format string) Middleware {
return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) {
return handler(ctx, e.Add(field.Time(key, time.Now())))
return handler(ctx, e.Add(field.FormatTime(key, format, time.Now())))
}
}

162
writter.go

@ -1,119 +1,129 @@
package log
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"sync"
"gitoa.ru/go-4devs/log/entry"
"gitoa.ru/go-4devs/log/field"
"gitoa.ru/go-4devs/log/internal/buffer"
)
// New creates standart logger.
func New(opts ...Option) Logger {
logger := log{e: stringFormat(), w: os.Stderr}
// 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"
)
for _, opt := range opts {
opt(&logger)
func WithWriter(w io.Writer) func(*option) {
return func(o *option) {
o.out = w
}
}
return func(_ context.Context, entry *entry.Entry) (int, error) {
b, err := logger.e(entry)
if err != nil {
return 0, fmt.Errorf("enode err: %w", err)
}
n, err := logger.w.Write(b)
if err != nil {
return 0, fmt.Errorf("failed write: %w", err)
}
return n, nil
func WithStdout() func(*option) {
return func(o *option) {
o.out = os.Stdout
}
}
// Option configure log.
type Option func(*log)
// Encode sets formats and encode output message.
type Encode func(*entry.Entry) ([]byte, error)
type log struct {
w io.Writer
e Encode
// WithStringFormat sets format as simple string.
func WithStringFormat() func(*option) {
return func(o *option) {
o.format = formatText()
}
}
// WithWriter sets writer logger.
func WithWriter(writer io.Writer) Option {
return func(l *log) {
l.w = writer
// WithJSONFormat sets json output format.
func WithJSONFormat() func(*option) {
return func(o *option) {
o.format = formatJSON()
}
}
// WithStdout sets logged to os.Stdout.
func WithStdout() Option {
return WithWriter(os.Stdout)
type option struct {
format func(io.Writer, *entry.Entry) (int, error)
out io.Writer
}
// WithEncode sets format log.
func WithEncode(e Encode) Option {
return func(l *log) {
l.e = e
// New creates standart logger.
func New(opts ...func(*option)) Logger {
log := option{
format: formatText(),
out: os.Stderr,
}
}
// WithStringFormat sets format as simple string.
func WithStringFormat() Option {
return WithEncode(stringFormat())
}
// WithJSONFormat sets json output format.
func WithJSONFormat() Option {
return WithEncode(jsonFormat)
}
for _, opt := range opts {
opt(&log)
}
//nolint:forcetypeassert
func stringFormat() func(entry *entry.Entry) ([]byte, error) {
pool := sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
return func(_ context.Context, entry *entry.Entry) (int, error) {
return log.format(log.out, entry)
}
}
return func(entry *entry.Entry) ([]byte, error) {
buf := pool.Get().(*bytes.Buffer)
buf.Reset()
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() {
pool.Put(buf)
buf.Free()
}()
buf.WriteString("msg=\"")
buf.WriteString(strings.TrimSpace(entry.Message()))
buf.WriteString("\"")
*buf = enc.AppendField(*buf, field.String(KeyMessage, entry.Message()))
for _, field := range entry.Fields() {
buf.WriteString(" ")
buf.WriteString(string(field.Key()))
buf.WriteString("=")
buf.WriteString(field.Value().String())
*buf = enc.AppendField(*buf, field)
}
buf.WriteString("\n")
_, _ = buf.WriteString("\n")
n, err := w.Write(*buf)
if err != nil {
return 0, fmt.Errorf("format text:%w", err)
}
return buf.Bytes(), nil
return n, nil
}
}
func jsonFormat(entry *entry.Entry) ([]byte, error) {
res, err := json.Marshal(entry.AddString("msg", entry.Message()).Fields().AsMap())
if err != nil {
return nil, fmt.Errorf("marshal err: %w", err)
}
func formatJSON() func(w io.Writer, entry *entry.Entry) (int, error) {
enc := field.NewEncoderJSON()
return append(res, []byte("\n")...), nil
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
}
}

Loading…
Cancel
Save