andrey
12 months ago
28 changed files with 1498 additions and 1712 deletions
@ -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, '"') |
|||
} |
|||
|
@ -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...) |
|||
} |
@ -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) |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
package field |
|||
|
|||
import ( |
|||
"fmt" |
|||
) |
|||
|
|||
func NewEncoderText(opts ...func(*BaseEncoder)) BaseEncoder { |
|||
opts = append([]func(*BaseEncoder){ |
|||
WithGropuConfig(0, 0, ' '), |
|||
WithNullValue("<nil>"), |
|||
WithDefaultValue(func(dst []byte, _ Encoder, val Value) []byte { |
|||
return fmt.Appendf(dst, "%+v", val.Any()) |
|||
}), |
|||
}, opts...) |
|||
|
|||
return NewEncoder(opts...) |
|||
} |
@ -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), |
|||
} |
|||
} |
@ -0,0 +1,24 @@ |
|||
package field |
|||
|
|||
//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
|
|||
) |
@ -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]] |
|||
} |
@ -0,0 +1,11 @@ |
|||
package field |
|||
|
|||
type LogValuer interface { |
|||
LogValue() any |
|||
} |
|||
|
|||
type ClosureFn func() any |
|||
|
|||
func (v ClosureFn) LogValue() any { |
|||
return v() |
|||
} |
@ -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, |
|||
} |
@ -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 |
|||
} |
File diff suppressed because it is too large
@ -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) |
|||
} |
@ -1,119 +1,129 @@ |
|||
package log |
|||
|
|||
import ( |
|||
"bytes" |
|||
"context" |
|||
"encoding/json" |
|||
"fmt" |
|||
"io" |
|||
"os" |
|||
"strings" |
|||
"sync" |
|||
|
|||
"gitoa.ru/go-4devs/log/entry" |
|||
"gitoa.ru/go-4devs/log/field" |
|||
"gitoa.ru/go-4devs/log/internal/buffer" |
|||
) |
|||
|
|||
// New creates standart logger.
|
|||
func New(opts ...Option) Logger { |
|||
logger := log{e: stringFormat(), w: os.Stderr} |
|||
// Keys for "built-in" attributes.
|
|||
const ( |
|||
// TimeKey is the key used by the built-in handlers for the time
|
|||
// when the log method is called. The associated Value is a [time.Time].
|
|||
KeyTime = "time" |
|||
// LevelKey is the key used by the built-in handlers for the level
|
|||
// of the log call. The associated value is a [Level].
|
|||
KeyLevel = "level" |
|||
// MessageKey is the key used by the built-in handlers for the
|
|||
// message of the log call. The associated value is a string.
|
|||
KeyMessage = "msg" |
|||
// SourceKey is the key used by the built-in handlers for the source file
|
|||
// and line of the log call. The associated value is a string.
|
|||
KeySource = "source" |
|||
) |
|||
|
|||
for _, opt := range opts { |
|||
opt(&logger) |
|||
func WithWriter(w io.Writer) func(*option) { |
|||
return func(o *option) { |
|||
o.out = w |
|||
} |
|||
} |
|||
|
|||
return func(_ context.Context, entry *entry.Entry) (int, error) { |
|||
b, err := logger.e(entry) |
|||
if err != nil { |
|||
return 0, fmt.Errorf("enode err: %w", err) |
|||
} |
|||
|
|||
n, err := logger.w.Write(b) |
|||
if err != nil { |
|||
return 0, fmt.Errorf("failed write: %w", err) |
|||
} |
|||
|
|||
return n, nil |
|||
func WithStdout() func(*option) { |
|||
return func(o *option) { |
|||
o.out = os.Stdout |
|||
} |
|||
} |
|||
|
|||
// Option configure log.
|
|||
type Option func(*log) |
|||
|
|||
// Encode sets formats and encode output message.
|
|||
type Encode func(*entry.Entry) ([]byte, error) |
|||
|
|||
type log struct { |
|||
w io.Writer |
|||
e Encode |
|||
// WithStringFormat sets format as simple string.
|
|||
func WithStringFormat() func(*option) { |
|||
return func(o *option) { |
|||
o.format = formatText() |
|||
} |
|||
} |
|||
|
|||
// WithWriter sets writer logger.
|
|||
func WithWriter(writer io.Writer) Option { |
|||
return func(l *log) { |
|||
l.w = writer |
|||
// WithJSONFormat sets json output format.
|
|||
func WithJSONFormat() func(*option) { |
|||
return func(o *option) { |
|||
o.format = formatJSON() |
|||
} |
|||
} |
|||
|
|||
// WithStdout sets logged to os.Stdout.
|
|||
func WithStdout() Option { |
|||
return WithWriter(os.Stdout) |
|||
type option struct { |
|||
format func(io.Writer, *entry.Entry) (int, error) |
|||
out io.Writer |
|||
} |
|||
|
|||
// WithEncode sets format log.
|
|||
func WithEncode(e Encode) Option { |
|||
return func(l *log) { |
|||
l.e = e |
|||
// New creates standart logger.
|
|||
func New(opts ...func(*option)) Logger { |
|||
log := option{ |
|||
format: formatText(), |
|||
out: os.Stderr, |
|||
} |
|||
} |
|||
|
|||
// WithStringFormat sets format as simple string.
|
|||
func WithStringFormat() Option { |
|||
return WithEncode(stringFormat()) |
|||
} |
|||
|
|||
// WithJSONFormat sets json output format.
|
|||
func WithJSONFormat() Option { |
|||
return WithEncode(jsonFormat) |
|||
} |
|||
for _, opt := range opts { |
|||
opt(&log) |
|||
} |
|||
|
|||
//nolint:forcetypeassert
|
|||
func stringFormat() func(entry *entry.Entry) ([]byte, error) { |
|||
pool := sync.Pool{ |
|||
New: func() interface{} { |
|||
return &bytes.Buffer{} |
|||
}, |
|||
return func(_ context.Context, entry *entry.Entry) (int, error) { |
|||
return log.format(log.out, entry) |
|||
} |
|||
} |
|||
|
|||
return func(entry *entry.Entry) ([]byte, error) { |
|||
buf := pool.Get().(*bytes.Buffer) |
|||
buf.Reset() |
|||
func formatText() func(io.Writer, *entry.Entry) (int, error) { |
|||
enc := field.NewEncoderText() |
|||
|
|||
return func(w io.Writer, entry *entry.Entry) (int, error) { |
|||
buf := buffer.New() |
|||
defer func() { |
|||
pool.Put(buf) |
|||
buf.Free() |
|||
}() |
|||
|
|||
buf.WriteString("msg=\"") |
|||
buf.WriteString(strings.TrimSpace(entry.Message())) |
|||
buf.WriteString("\"") |
|||
*buf = enc.AppendField(*buf, field.String(KeyMessage, entry.Message())) |
|||
|
|||
for _, field := range entry.Fields() { |
|||
buf.WriteString(" ") |
|||
buf.WriteString(string(field.Key())) |
|||
buf.WriteString("=") |
|||
buf.WriteString(field.Value().String()) |
|||
*buf = enc.AppendField(*buf, field) |
|||
} |
|||
|
|||
buf.WriteString("\n") |
|||
_, _ = buf.WriteString("\n") |
|||
|
|||
n, err := w.Write(*buf) |
|||
if err != nil { |
|||
return 0, fmt.Errorf("format text:%w", err) |
|||
} |
|||
|
|||
return buf.Bytes(), nil |
|||
return n, nil |
|||
} |
|||
} |
|||
|
|||
func jsonFormat(entry *entry.Entry) ([]byte, error) { |
|||
res, err := json.Marshal(entry.AddString("msg", entry.Message()).Fields().AsMap()) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("marshal err: %w", err) |
|||
} |
|||
func formatJSON() func(w io.Writer, entry *entry.Entry) (int, error) { |
|||
enc := field.NewEncoderJSON() |
|||
|
|||
return append(res, []byte("\n")...), nil |
|||
return func(w io.Writer, entry *entry.Entry) (int, error) { |
|||
buf := buffer.New() |
|||
defer func() { |
|||
buf.Free() |
|||
}() |
|||
|
|||
_, _ = buf.WriteString("{") |
|||
*buf = enc.AppendField(*buf, field.String(KeyMessage, entry.Message())) |
|||
|
|||
for _, field := range entry.Fields() { |
|||
*buf = enc.AppendField(*buf, field) |
|||
} |
|||
|
|||
_, _ = buf.WriteString("}") |
|||
_, _ = buf.WriteString("\n") |
|||
|
|||
n, err := w.Write(*buf) |
|||
if err != nil { |
|||
return 0, fmt.Errorf("format json:%w", err) |
|||
} |
|||
|
|||
return n, nil |
|||
} |
|||
} |
|||
|
Loading…
Reference in new issue