diff --git a/.drone.yml b/.drone.yml index 8eaa186..e41e5b8 100644 --- a/.drone.yml +++ b/.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 diff --git a/.golangci.yml b/.golangci.yml index 47cef9c..1491609 100644 --- a/.golangci.yml +++ b/.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 diff --git a/bench_test.go b/bench_test.go index 9d003f0..0ee96c1 100644 --- a/bench_test.go +++ b/bench_test.go @@ -12,7 +12,6 @@ import ( "gitoa.ru/go-4devs/log/field" ) -//nolint:gochecknoglobals var ( errExample = errors.New("fail") _messages = fakeMessages(1000) diff --git a/field/encoder.go b/field/encoder.go index 905eb2e..86497c1 100644 --- a/field/encoder.go +++ b/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, '"') } diff --git a/field/encoder_json.go b/field/encoder_json.go new file mode 100644 index 0000000..23bf77e --- /dev/null +++ b/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...) +} diff --git a/field/encoder_json_test.go b/field/encoder_json_test.go new file mode 100644 index 0000000..f910f91 --- /dev/null +++ b/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) + } +} diff --git a/field/encoder_text.go b/field/encoder_text.go new file mode 100644 index 0000000..073a157 --- /dev/null +++ b/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(""), + WithDefaultValue(func(dst []byte, _ Encoder, val Value) []byte { + return fmt.Appendf(dst, "%+v", val.Any()) + }), + }, opts...) + + return NewEncoder(opts...) +} diff --git a/field/errors.go b/field/errors.go new file mode 100644 index 0000000..a00ddde --- /dev/null +++ b/field/errors.go @@ -0,0 +1,5 @@ +package field + +import "errors" + +var ErrUndefined = errors.New("indefined") diff --git a/field/field.go b/field/field.go index a3029d3..d6ad541 100644 --- a/field/field.go +++ b/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) } diff --git a/field/fields.go b/field/fields.go index f919dd6..60ac5b5 100644 --- a/field/fields.go +++ b/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 -} diff --git a/field/key.go b/field/key.go deleted file mode 100644 index febd38d..0000000 --- a/field/key.go +++ /dev/null @@ -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), - } -} diff --git a/field/kind.go b/field/kind.go new file mode 100644 index 0000000..4dbee69 --- /dev/null +++ b/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) +} diff --git a/field/kind_string.go b/field/kind_string.go new file mode 100644 index 0000000..3803894 --- /dev/null +++ b/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]] +} diff --git a/field/log_valuer.go b/field/log_valuer.go new file mode 100644 index 0000000..f290a6f --- /dev/null +++ b/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() +} diff --git a/field/safe_set.go b/field/safe_set.go new file mode 100644 index 0000000..5815208 --- /dev/null +++ b/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, +} diff --git a/field/type.go b/field/type.go deleted file mode 100644 index 17f9a3d..0000000 --- a/field/type.go +++ /dev/null @@ -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 -} diff --git a/field/value.go b/field/value.go index c8b4283..7681786 100644 --- a/field/value.go +++ b/field/value.go @@ -1,862 +1,651 @@ +//nolint: exhaustruct package field import ( - "encoding/json" "fmt" "math" "strconv" "time" + "unsafe" ) -type Value struct { - vtype Type - numeric uint64 - stringly string - value interface{} -} - -func (v Value) MarshalJSON() ([]byte, error) { - b, err := json.Marshal(v.AsInterface()) - if err != nil { - return nil, fmt.Errorf("marshal err: %w", err) +// 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, } - - return b, nil } -//nolint:gocyclo,gomnd,cyclop -func (v Value) String() string { - switch { - case v.vtype.IsArray(), v.vtype.IsAny(): - return fmt.Sprintf("%+v", v.AsInterface()) - case v.vtype.IsNil(): - return "" - case v.vtype.IsString(): - return v.asString() - case v.vtype.IsBool(): - return strconv.FormatBool(v.asBool()) - case v.vtype.IsInt(), v.vtype.IsInt8(), v.vtype.IsInt16(), v.vtype.IsInt32(): - return strconv.Itoa(v.asInt()) - case v.vtype.IsInt64(): - return strconv.FormatInt(v.asInt64(), 10) - case v.vtype.IsUint(), v.vtype.IsUint8(), v.vtype.IsUint16(), v.vtype.IsUint32(), v.vtype.IsUint64(): - return strconv.FormatUint(v.asUint64(), 10) - case v.vtype.IsFloat64(): - return strconv.FormatFloat(v.asFloat64(), 'g', -1, 64) - case v.vtype.IsFloat32(): - return strconv.FormatFloat(float64(v.asFloat32()), 'g', -1, 32) - case v.vtype.IsComplex128(): - return strconv.FormatComplex(v.asComplex128(), 'g', -1, 128) - case v.vtype.IsComplex64(): - return strconv.FormatComplex(complex128(v.asComplex64()), 'g', -1, 64) - case v.vtype.IsBinary(): - return string(v.asBinary()) - case v.vtype.IsDuration(): - return v.asDuration().String() - case v.vtype.IsTime(): - return v.asTime().Format(v.asString()) - case v.vtype.IsError(): - return v.asError().Error() - } - - return fmt.Sprintf("%+v", v.AsInterface()) -} - -//nolint:gocyclo,cyclop -func (v Value) AsInterface() interface{} { - switch { - case v.vtype.IsArray(): - return v.value - case v.vtype.IsNil(): - return nil - case v.vtype.IsString(): - return v.asString() - case v.vtype.IsBool(): - return v.asBool() - case v.vtype.IsInt(): - return v.asInt() - case v.vtype.IsInt8(): - return v.asInt8() - case v.vtype.IsInt16(): - return v.asInt16() - case v.vtype.IsInt32(): - return v.asInt32() - case v.vtype.IsInt64(): - return v.asInt64() - case v.vtype.IsUint(): - return v.asUint() - case v.vtype.IsUint8(): - return v.asUint8() - case v.vtype.IsUint16(): - return v.asUint16() - case v.vtype.IsUint32(): - return v.asUint32() - case v.vtype.IsUint64(): - return v.asUint64() - case v.vtype.IsFloat32(): - return v.asFloat32() - case v.vtype.IsFloat64(): - return v.asFloat64() - case v.vtype.IsComplex64(): - return v.asComplex64() - case v.vtype.IsComplex128(): - return v.asComplex128() - case v.vtype.IsUintptr(): - return v.asUintptr() - case v.vtype.IsBinary(): - return v.asBinary() - case v.vtype.IsDuration(): - return v.asDuration() - case v.vtype.IsTime(): - return v.asTime() - case v.vtype.IsError(): - return v.asError() +// StringpValue returns a new Value for a *string. +func StringpValue(value *string) Value { + if value == nil { + return NilValue() } - return v.value -} - -func (v Value) IsArray() bool { - return v.vtype.IsArray() -} - -func (v Value) IsNil() bool { - return v.vtype.IsNil() -} - -func (v Value) IsString() bool { - return v.vtype.IsString() -} - -func (v Value) IsBool() bool { - return v.vtype.IsBool() -} - -func (v Value) IsInt() bool { - return v.vtype.IsInt() -} - -func (v Value) IsInt8() bool { - return v.vtype.IsInt8() -} - -func (v Value) IsInt16() bool { - return v.vtype.IsInt16() -} - -func (v Value) IsInt32() bool { - return v.vtype.IsInt32() -} - -func (v Value) IsInt64() bool { - return v.vtype.IsInt64() -} - -func (v Value) IsUint() bool { - return v.vtype.IsUint() -} - -func (v Value) IsUint8() bool { - return v.vtype.IsUint8() -} - -func (v Value) IsUint16() bool { - return v.vtype.IsUint16() -} - -func (v Value) IsUint32() bool { - return v.vtype.IsUint32() -} - -func (v Value) IsUint64() bool { - return v.vtype.IsUint64() -} - -func (v Value) IsFloat32() bool { - return v.vtype.IsFloat32() -} - -func (v Value) IsFloat64() bool { - return v.vtype.IsFloat64() -} - -func (v Value) IsComplex64() bool { - return v.vtype.IsComplex64() -} - -func (v Value) IsComplex128() bool { - return v.vtype.IsComplex128() -} - -func (v Value) IsUintptr() bool { - return v.vtype.IsUintptr() -} - -func (v Value) IsBinary() bool { - return v.vtype.IsBinary() -} - -func (v Value) IsDuration() bool { - return v.vtype.IsDuration() -} - -func (v Value) IsTime() bool { - return v.vtype.IsTime() -} - -func (v Value) IsError() bool { - return v.vtype.IsError() -} - -func (v Value) asString() string { - return v.stringly -} - -func (v Value) asBool() bool { - return v.numeric == 1 -} - -func (v Value) asInt() int { - return int(v.numeric) -} - -func (v Value) asInt8() int8 { - return int8(v.numeric) -} - -func (v Value) asInt16() int16 { - return int16(v.numeric) -} - -func (v Value) asInt32() int32 { - return int32(v.numeric) -} - -func (v Value) asInt64() int64 { - return int64(v.numeric) -} - -func (v Value) asUint() uint { - return uint(v.numeric) -} - -func (v Value) asUint8() uint8 { - return uint8(v.numeric) -} - -func (v Value) asUint16() uint16 { - return uint16(v.numeric) -} - -func (v Value) asUint32() uint32 { - return uint32(v.numeric) -} - -func (v Value) asUint64() uint64 { - return v.numeric -} - -func (v Value) asFloat32() float32 { - return math.Float32frombits(uint32(v.numeric)) -} - -func (v Value) asFloat64() float64 { - return math.Float64frombits(v.numeric) -} - -func (v Value) asComplex64() complex64 { - cmplex, _ := v.value.(complex64) - - return cmplex -} - -func (v Value) asComplex128() complex128 { - cmplex, _ := v.value.(complex128) - - return cmplex -} - -func (v Value) asUintptr() uintptr { - val, _ := v.value.(uintptr) - - return val -} - -func (v Value) asBinary() []byte { - bytes, _ := v.value.([]byte) - - return bytes -} - -func (v Value) asDuration() time.Duration { - duration, _ := v.value.(time.Duration) - - return duration -} - -func (v Value) asTime() time.Time { - value, _ := v.value.(time.Time) - - return value -} - -func (v Value) asError() error { - err, _ := v.value.(error) - - return err -} - -func nilValue(t Type) Value { - return Value{ - vtype: t | TypeNil, - value: nil, - numeric: 0, - stringly: "", - } + return StringValue(*value) } -func stringValue(v string) Value { +// StringpValue returns a new Value for a string. +func StringsValue(value []string) Value { return Value{ - stringly: v, - vtype: TypeString, - numeric: 0, - value: nil, - } -} + Kind: KindArray, + num: uint64(len(value)), + any: func() []Value { + values := make([]Value, len(value)) + for idx := range value { + values[idx] = StringValue(value[idx]) + } -func stringsValue(v []string) Value { - return Value{ - value: v, - vtype: TypeString | TypeArray, - numeric: 0, - stringly: "", + return values + }, } } -func stringpValue(v *string) Value { - if v != nil { - return stringValue(*v) +// BoolValue returns a Value for a bool. +func BoolValue(v bool) Value { + u := uint64(0) + if v { + u = 1 } - return nilValue(TypeString) + return Value{num: u, Kind: KindBool} } -func boolValue(b bool) Value { - if b { - return Value{ - numeric: 1, - vtype: TypeBool, - value: nil, - stringly: "", - } - } - +// BoolsValue returns a Value for a []bool. +func BoolsValue(values []bool) Value { return Value{ - vtype: TypeBool, - value: nil, - numeric: 0, - stringly: "", - } -} + Kind: KindArray, + num: uint64(len(values)), + any: func() []Value { + vals := make([]Value, len(values)) + for idx := range values { + vals[idx] = BoolValue(values[idx]) + } -func boolsValue(b []bool) Value { - return Value{ - value: b, - vtype: TypeBool | TypeArray, - numeric: 0, - stringly: "", + return vals + }, } } -func boolpValue(b *bool) Value { - if b != nil { - return boolValue(*b) +// BoolpValue returns a new Value for a *bool. +func BoolpValue(value *bool) Value { + if value == nil { + return NilValue() } - return nilValue(TypeBool) + return BoolValue(*value) } -func intValue(i int) Value { - return Value{ - vtype: TypeInt, - numeric: uint64(i), - value: nil, - stringly: "", - } +// Uint64Value returns a Value for a uint64. +func Uint64Value(v uint64) Value { + return Value{num: v, Kind: KindUint64} } -func intsValue(i []int) Value { +// Uint64sValue returns a Value for a []uint64. +func Uint64sValue(values []uint64) Value { return Value{ - value: i, - vtype: TypeInt | TypeArray, - numeric: 0, - stringly: "", - } -} + Kind: KindArray, + num: uint64(len(values)), + any: func() []Value { + vals := make([]Value, len(values)) + for idx := range values { + vals[idx] = Uint64Value(values[idx]) + } -func intpValue(in *int) Value { - if in != nil { - return intValue(*in) + return vals + }, } - - return nilValue(TypeInt) } -func int8Value(i int8) Value { +// Uint8sValue returns a Value for a []uint8. +func Uint8sValue(values []uint8) Value { return Value{ - vtype: TypeInt8, - numeric: uint64(i), - value: nil, - stringly: "", - } -} + Kind: KindArray, + num: uint64(len(values)), + any: func() []Value { + vals := make([]Value, len(values)) + for idx := range values { + vals[idx] = Uint64Value(uint64(values[idx])) + } -func int8sValue(i []int8) Value { - return Value{ - value: i, - vtype: TypeInt8 | TypeArray, - numeric: 0, - stringly: "", + return vals + }, } } -func int8pValue(in *int8) Value { - if in != nil { - return int8Value(*in) +// Uint64sValue returns a Value for a []uint64. +func Uint64pValue(v *uint64) Value { + if v == nil { + return NilValue() } - return nilValue(TypeInt8) + return Uint64Value(*v) } -func int16Value(i int16) Value { - return Value{ - vtype: TypeInt16, - numeric: uint64(i), - value: 0, - stringly: "", - } +// Int64Value returns a Value for an int64. +func Int64Value(value int64) Value { + return Value{inum: value, Kind: KindInt64} } -func int16sValue(i []int16) Value { +// Int64sValue returns a Value for an []int64. +func Int64sValue(value []int64) Value { return Value{ - value: i, - vtype: TypeInt16 | TypeArray, - numeric: 0, - stringly: "", - } -} + Kind: KindArray, + num: uint64(len(value)), + any: func() []Value { + vals := make([]Value, len(value)) + for idx := range value { + vals[idx] = Int64Value(value[idx]) + } -func int16pValue(in *int16) Value { - if in != nil { - return int16Value(*in) + return vals + }, } - - return nilValue(TypeInt16) } -func int32Value(i int32) Value { - return Value{ - vtype: TypeInt32, - numeric: uint64(i), - value: nil, - stringly: "", +// Int64sValue returns a Value for an *int64. +func Int64pValue(value *int64) Value { + if value == nil { + return NilValue() } -} -func int32sValue(i []int32) Value { - return Value{ - value: i, - vtype: TypeInt32 | TypeArray, - numeric: 0, - stringly: "", - } + return Int64Value(*value) } -func int32pValue(in *int32) Value { - if in != nil { - return int32Value(*in) - } - - return nilValue(TypeInt32) +// Float64Value returns a Value for a floating-point number. +func Float64Value(v float64) Value { + return Value{num: math.Float64bits(v), Kind: KindFloat64} } -func int64Value(i int64) Value { +// Float64Value returns a Value for a floating-points number. +func Float64sValue(values []float64) Value { return Value{ - vtype: TypeInt64, - numeric: uint64(i), - value: nil, - stringly: "", - } -} + Kind: KindArray, + num: uint64(len(values)), + any: func() []Value { + vals := make([]Value, len(values)) + for idx := range values { + vals[idx] = Float64Value(values[idx]) + } -func int64sValue(i []int64) Value { - return Value{ - value: i, - vtype: TypeInt64 | TypeArray, - numeric: 0, - stringly: "", + return vals + }, } } -func int64pValue(in *int64) Value { - if in != nil { - return int64Value(*in) +// Float64Value returns a Value for a floating-points number. +func Float64pValue(v *float64) Value { + if v == nil { + return NilValue() } - return nilValue(TypeInt64) + return Float64Value(*v) } -func uintValue(i uint) Value { +// Complex64sValue returns a Value for a []complex64. +func Complex64sValue(values []complex64) Value { return Value{ - vtype: TypeUint, - numeric: uint64(i), - value: nil, - stringly: "", - } -} - -func uintsValue(i []uint) Value { - return Value{ - value: i, - vtype: TypeUint | TypeArray, - numeric: 0, - stringly: "", - } -} + Kind: KindArray, + num: uint64(len(values)), + any: func() []Value { + vals := make([]Value, len(values)) + for idx := range values { + vals[idx] = Complex128Value(complex128(values[idx])) + } -func uintpValue(in *uint) Value { - if in != nil { - return uintValue(*in) + return vals + }, } - - return nilValue(TypeUint) } -func uint8Value(i uint8) Value { +// Complex128Value returns a Value for a complex128. +func Complex128Value(v complex128) Value { return Value{ - vtype: TypeUint8, - numeric: uint64(i), - value: nil, - stringly: "", + Kind: KindComplex128, + any: v, } } -func uint8sValue(i []uint8) Value { +// Complex128Value returns a Value for a []complex128. +func Complex128sValue(values []complex128) Value { return Value{ - value: i, - vtype: TypeUint8 | TypeArray, - numeric: 0, - stringly: "", - } -} + Kind: KindArray, + num: uint64(len(values)), + any: func() []Value { + vals := make([]Value, len(values)) + for idx := range values { + vals[idx] = Complex128Value(values[idx]) + } -func uint8pValue(in *uint8) Value { - if in != nil { - return uint8Value(*in) + return vals + }, } - - return nilValue(TypeUint8) } -func uint16Value(i uint16) Value { - return Value{ - vtype: TypeUint16, - numeric: uint64(i), - value: nil, - stringly: "", +// Complex128Value returns a Value for a *complex128. +func Complex128pValue(v *complex128) Value { + if v == nil { + return NilValue() } -} -func uint16sValue(i []uint16) Value { - return Value{ - value: i, - vtype: TypeUint16 | TypeArray, - numeric: 0, - stringly: "", - } + return Complex128Value(*v) } -func uint16pValue(in *uint16) Value { - if in != nil { - return uint16Value(*in) - } - - return nilValue(TypeUint16) +// TimeValue returns a Value for a time.Time. +func TimeValue(v time.Time) Value { + return Value{inum: v.UnixNano(), any: v.Location(), Kind: KindTime} } -func uint32Value(i uint32) Value { - return Value{ - vtype: TypeUint32, - numeric: uint64(i), - value: nil, - stringly: "", +// TimepValue returns a Value for a *time.Time. +func TimepValue(v *time.Time) Value { + if v == nil { + return NilValue() } + + return TimeValue(*v) } -func uint32sValue(i []uint32) Value { +// TimesValue returns a Value for a []time.Time. +func TimesValue(values []time.Time) Value { return Value{ - value: i, - vtype: TypeUint32 | TypeArray, - numeric: 0, - stringly: "", - } -} + Kind: KindArray, + num: uint64(len(values)), + any: func() []Value { + vals := make([]Value, len(values)) + for idx := range values { + vals[idx] = TimeValue(values[idx]) + } -func uint32pValue(in *uint32) Value { - if in != nil { - return uint32Value(*in) + return vals + }, } - - return nilValue(TypeUint32) } -func uint64Value(i uint64) Value { +func ClosureValue(fn ClosureFn) Value { return Value{ - vtype: TypeUint64, - numeric: i, - value: nil, - stringly: "", + Kind: KindClosure, + any: fn, } } -func uint64sValue(i []uint64) Value { - return Value{ - value: i, - vtype: TypeUint64 | TypeArray, - numeric: 0, - stringly: "", - } +// DurationValue returns a Value for a time.Duration. +func DurationValue(v time.Duration) Value { + return Value{inum: v.Nanoseconds(), Kind: KindDuration} } -func uint64pValue(in *uint64) Value { - if in != nil { - return uint64Value(*in) +// DurationValue returns a Value for a *time.Duration. +func DurationpValue(v *time.Duration) Value { + if v == nil { + return NilValue() } - return nilValue(TypeUint64) + return DurationValue(*v) } -func float32Value(i float32) Value { +// DurationValue returns a Value for a *time.Duration. +func DurationsValue(values []time.Duration) Value { return Value{ - vtype: TypeFloat32, - numeric: uint64(math.Float32bits(i)), - value: nil, - stringly: "", - } -} + Kind: KindArray, + num: uint64(len(values)), + any: func() []Value { + vals := make([]Value, len(values)) + for idx := range values { + vals[idx] = DurationValue(values[idx]) + } -func float32sValue(i []float32) Value { - return Value{ - value: i, - vtype: TypeFloat32 | TypeArray, - numeric: 0, - stringly: "", + return vals + }, } } -func float32pValue(in *float32) Value { - if in != nil { - return float32Value(*in) +// 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, } - - return nilValue(TypeFloat32) } -func float64Value(i float64) Value { +func ErrorValue(value error) Value { return Value{ - vtype: TypeFloat64, - numeric: math.Float64bits(i), - value: nil, - stringly: "", + Kind: KindError, + any: value, } } -func float64sValue(i []float64) Value { +func ErrorsValue(value []error) Value { return Value{ - value: i, - vtype: TypeFloat64 | TypeArray, - numeric: 0, - stringly: "", - } -} + Kind: KindArray, + num: uint64(len(value)), + any: func() []Value { + vals := make([]Value, len(value)) + for idx := range value { + vals[idx] = ErrorValue(value[idx]) + } -func float64pValue(in *float64) Value { - if in != nil { - return float64Value(*in) + return vals + }, } - - return nilValue(TypeFloat64) } -func complex64Value(in complex64) Value { - return Value{ - vtype: TypeComplex64, - value: in, - numeric: 0, - stringly: "", - } +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 complex64sValue(in []complex64) Value { - return Value{ - vtype: TypeComplex64 | TypeArray, - value: in, - numeric: 0, - stringly: "", +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 } -func complex64pValue(in *complex64) Value { - if in != nil { - return complex64Value(*in) +//nolint: forcetypeassert +func (v Value) AsString() string { + if v.Kind != KindString { + return "" } - return nilValue(TypeComplex64) + return unsafe.String(v.any.(stringptr), v.num) } -func complex128Value(in complex128) Value { - return Value{ - vtype: TypeComplex64, - value: in, - numeric: 0, - stringly: "", - } +func (v Value) AsBool() bool { + return v.num == 1 } -func complex128sValue(in []complex128) Value { - return Value{ - vtype: TypeComplex128 | TypeArray, - value: in, - numeric: 0, - stringly: "", - } +func (v Value) AsInt64() int64 { + return v.inum } -func complex128pValue(in *complex128) Value { - if in != nil { - return complex128Value(*in) - } +func (v Value) AsUint() uint { + return uint(v.num) +} - return nilValue(TypeComplex128) +func (v Value) AsUint64() uint64 { + return v.num } -func uintptrValue(in uintptr) Value { - return Value{ - vtype: TypeUintptr, - numeric: 0, - stringly: "", - value: in, - } +func (v Value) AsFloat32() float32 { + return math.Float32frombits(uint32(v.num)) } -func uintptrsValue(in []uintptr) Value { - return Value{ - vtype: TypeUintptr | TypeArray, - value: in, - numeric: 0, - stringly: "", - } +func (v Value) AsFloat64() float64 { + return math.Float64frombits(v.num) } -func uintptrpValue(in *uintptr) Value { - if in != nil { - return uintptrValue(*in) - } +func (v Value) AsComplex128() complex128 { + cmplex, _ := v.any.(complex128) - return nilValue(TypeUintptr) + return cmplex } -func bytesValue(in []byte) Value { - return Value{ - vtype: TypeBinary, - value: in, - numeric: 0, - stringly: "", - } +func (v Value) AsUintptr() uintptr { + return uintptr(v.num) } -func durationValue(in time.Duration) Value { - return Value{ - vtype: TypeDuration, - value: in, - numeric: 0, - stringly: "", - } +func (v Value) AsBinary() []byte { + bytes, _ := v.any.([]byte) + + return bytes } -func durationsValue(in []time.Duration) Value { - return Value{ - vtype: TypeDuration | TypeArray, - value: in, - numeric: 0, - stringly: "", - } +func (v Value) AsDuration() time.Duration { + return time.Duration(v.inum) } -func durationpValue(in *time.Duration) Value { - if in != nil { - return durationValue(*in) +func (v Value) AsTime() time.Time { + loc, ok := v.any.(*time.Location) + if !ok { + return time.Time{} } - return nilValue(TypeDuration) + return time.Unix(0, v.inum).In(loc) } -func timeValue(in time.Time) Value { - return formatTimeValue(time.RFC3339, in) -} - -func timesValue(in []time.Time) Value { - return formatTimesValue(time.RFC3339, in) -} +func (v Value) AsError() error { + err, _ := v.any.(error) -func timepValue(in *time.Time) Value { - return formatTimepValue(time.RFC3339, in) + return err } -func formatTimeValue(format string, in time.Time) Value { - return Value{ - vtype: TypeTime, - value: in, - stringly: format, - numeric: 0, +//nolint:forcetypeassert +func (v Value) AsGroup() Fields { + if v.Kind != KindGroup { + return nil } -} -func formatTimesValue(format string, in []time.Time) Value { - return Value{ - vtype: TypeTime | TypeArray, - value: in, - stringly: format, - numeric: 0, - } + return unsafe.Slice((*Field)(v.any.(groupptr)), v.num) } -func formatTimepValue(format string, in *time.Time) Value { - if in != nil { - return formatTimeValue(format, *in) +func (v Value) Resolve() any { + cl, ok := v.any.(ClosureFn) + if !ok { + return nil } - return nilValue(TypeTime) + return cl() } -func errorValue(in error) Value { - if in != nil { - return Value{ - vtype: TypeError, - value: in, - numeric: 0, - stringly: "", - } +type Values []Value + +func (v Values) Resolve() any { + res := make([]any, len(v)) + for idx := range v { + res[idx] = v[idx].Any() } - return nilValue(TypeError) + return res } -func errorsValue(in []error) Value { - return Value{ - vtype: TypeError | TypeArray, - value: in, - numeric: 0, - stringly: "", +func (v Value) AsArray() Values { + switch res := v.any.(type) { + case []Value: + return res + case func() []Value: + return res() + default: + return nil } } diff --git a/global.go b/global.go index 369d9be..b221912 100644 --- a/global.go +++ b/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), ) diff --git a/go.mod b/go.mod index 56bca9b..e260015 100644 --- a/go.mod +++ b/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 diff --git a/handler/logrus/logger.go b/handler/logrus/logger.go index 44f34dd..7059a91 100644 --- a/handler/logrus/logger.go +++ b/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) diff --git a/handler/otel/helpers.go b/handler/otel/helpers.go index 722393d..63b4558 100644 --- a/handler/otel/helpers.go +++ b/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...)) } diff --git a/handler/zap/logger.go b/handler/zap/logger.go index 29a7585..8cb3bd6 100644 --- a/handler/zap/logger.go +++ b/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: diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go new file mode 100644 index 0000000..19bb500 --- /dev/null +++ b/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) +} diff --git a/level/level_test.go b/level/level_test.go index d6b7b23..5f24c56 100644 --- a/level/level_test.go +++ b/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) } - } } } diff --git a/logger.go b/logger.go index a1e1d8f..f6a1236 100644 --- a/logger.go +++ b/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() diff --git a/logger_example_test.go b/logger_example_test.go index c572e89..7f21118 100644 --- a/logger_example_test.go +++ b/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 } diff --git a/logger_example_trace_test.go b/logger_example_trace_test.go index 7611a92..fc6cd08 100644 --- a/logger_example_trace_test.go +++ b/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) diff --git a/logger_test.go b/logger_test.go index a3752a5..cae645b 100644 --- a/logger_test.go +++ b/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)) diff --git a/middleware.go b/middleware.go index d59cc33..7aad519 100644 --- a/middleware.go +++ b/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()))) } } diff --git a/writter.go b/writter.go index ce4eeca..68f83bd 100644 --- a/writter.go +++ b/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 + } }