From deb67b0008509c3398b57999af58be9685de83c1 Mon Sep 17 00:00:00 2001 From: andrey1s Date: Sun, 19 Sep 2021 17:25:47 +0300 Subject: [PATCH] restore v0.3.0 --- .DS_Store | Bin 0 -> 6148 bytes bench_test.go | 177 ++++++ entry/caller.go | 22 + entry/entry.go | 132 ++++ entry/pool.go | 21 + field.go | 37 +- field/encoder.go | 31 + field/field.go | 333 ++++++++++ field/fields.go | 29 + field/key.go | 568 +++++++++++++++++ field/type.go | 126 ++++ field/value.go | 755 +++++++++++++++++++++++ global.go | 77 ++- go.mod | 9 +- go.sum | 76 +++ handler/logrus/logger.go | 90 +++ handler/logrus/logger_test.go | 36 ++ handler/otel/helpers.go | 56 ++ handler/otel/level.go | 16 + handler/otel/level_string.go | 51 ++ handler/otel/logger.go | 16 + handler/otel/middleware.go | 16 + handler/zap/logger.go | 95 +++ handler/zap/logger_test.go | 41 ++ level.go | 22 - level/level.go | 73 +++ level_string.go => level/level_string.go | 18 +- logger.go | 221 +++++-- logger_example_caller_test.go | 20 + logger_example_logrus_test.go | 21 + logger_example_test.go | 81 ++- logger_example_trace_test.go | 62 ++ logger_example_zap_test.go | 28 + logger_test.go | 85 ++- middleware.go | 124 ++-- std.go | 121 ---- writter.go | 118 ++++ 37 files changed, 3472 insertions(+), 332 deletions(-) create mode 100644 .DS_Store create mode 100644 bench_test.go create mode 100644 entry/caller.go create mode 100644 entry/entry.go create mode 100644 entry/pool.go create mode 100644 field/encoder.go create mode 100644 field/field.go create mode 100644 field/fields.go create mode 100644 field/key.go create mode 100644 field/type.go create mode 100644 field/value.go create mode 100644 go.sum create mode 100644 handler/logrus/logger.go create mode 100644 handler/logrus/logger_test.go create mode 100644 handler/otel/helpers.go create mode 100644 handler/otel/level.go create mode 100644 handler/otel/level_string.go create mode 100644 handler/otel/logger.go create mode 100644 handler/otel/middleware.go create mode 100644 handler/zap/logger.go create mode 100644 handler/zap/logger_test.go delete mode 100644 level.go create mode 100644 level/level.go rename level_string.go => level/level_string.go (76%) create mode 100644 logger_example_caller_test.go create mode 100644 logger_example_logrus_test.go create mode 100644 logger_example_trace_test.go create mode 100644 logger_example_zap_test.go delete mode 100644 std.go create mode 100644 writter.go diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 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 new file mode 100644 index 0000000..fc1cf8e --- /dev/null +++ b/field/value.go @@ -0,0 +1,755 @@ +package field + +import ( + "encoding/json" + "fmt" + "math" + "strconv" + "time" +) + +type Value struct { + vtype Type + numeric uint64 + stringly string + value interface{} +} + +func (v Value) MarshalJSON() ([]byte, error) { + return json.Marshal(v.AsInterface()) +} + +//nolint: gocyclo +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 +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() + } + + 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 { + return v.value.(complex64) +} + +func (v Value) asComplex128() complex128 { + return v.value.(complex128) +} + +func (v Value) asUintptr() uintptr { + return v.value.(uintptr) +} + +func (v Value) asBinary() []byte { + return v.value.([]byte) +} + +func (v Value) asDuration() time.Duration { + return v.value.(time.Duration) +} + +func (v Value) asTime() time.Time { + return v.value.(time.Time) +} + +func (v Value) asError() error { + return v.value.(error) +} + +func nilValue(t Type) Value { + return Value{vtype: t | TypeNil} +} + +func stringValue(v string) Value { + return Value{ + stringly: v, + vtype: TypeString, + } +} + +func stringsValue(v []string) Value { + return Value{ + value: v, + vtype: TypeString | TypeArray, + } +} + +func stringpValue(v *string) Value { + if v != nil { + return stringValue(*v) + } + + return nilValue(TypeString) +} + +func boolValue(b bool) Value { + if b { + return Value{ + numeric: 1, + vtype: TypeBool, + } + } + + return Value{ + vtype: TypeBool, + } +} + +func boolsValue(b []bool) Value { + return Value{ + value: b, + vtype: TypeBool | TypeArray, + } +} + +func boolpValue(b *bool) Value { + if b != nil { + return boolValue(*b) + } + + return nilValue(TypeBool) +} + +func intValue(i int) Value { + return Value{ + vtype: TypeInt, + numeric: uint64(i), + } +} + +func intsValue(i []int) Value { + return Value{ + value: i, + vtype: TypeInt | TypeArray, + } +} + +func intpValue(in *int) Value { + if in != nil { + return intValue(*in) + } + + return nilValue(TypeInt) +} + +func int8Value(i int8) Value { + return Value{ + vtype: TypeInt8, + numeric: uint64(i), + } +} + +func int8sValue(i []int8) Value { + return Value{ + value: i, + vtype: TypeInt8 | TypeArray, + } +} + +func int8pValue(in *int8) Value { + if in != nil { + return int8Value(*in) + } + + return nilValue(TypeInt8) +} + +func int16Value(i int16) Value { + return Value{ + vtype: TypeInt16, + numeric: uint64(i), + } +} + +func int16sValue(i []int16) Value { + return Value{ + value: i, + vtype: TypeInt16 | TypeArray, + } +} + +func int16pValue(in *int16) Value { + if in != nil { + return int16Value(*in) + } + + return nilValue(TypeInt16) +} + +func int32Value(i int32) Value { + return Value{ + vtype: TypeInt32, + numeric: uint64(i), + } +} + +func int32sValue(i []int32) Value { + return Value{ + value: i, + vtype: TypeInt32 | TypeArray, + } +} + +func int32pValue(in *int32) Value { + if in != nil { + return int32Value(*in) + } + + return nilValue(TypeInt32) +} + +func int64Value(i int64) Value { + return Value{ + vtype: TypeInt64, + numeric: uint64(i), + } +} + +func int64sValue(i []int64) Value { + return Value{ + value: i, + vtype: TypeInt64 | TypeArray, + } +} + +func int64pValue(in *int64) Value { + if in != nil { + return int64Value(*in) + } + + return nilValue(TypeInt64) +} + +func uintValue(i uint) Value { + return Value{ + vtype: TypeUint, + numeric: uint64(i), + } +} + +func uintsValue(i []uint) Value { + return Value{ + value: i, + vtype: TypeUint | TypeArray, + } +} + +func uintpValue(in *uint) Value { + if in != nil { + return uintValue(*in) + } + + return nilValue(TypeUint) +} + +func uint8Value(i uint8) Value { + return Value{ + vtype: TypeUint8, + numeric: uint64(i), + } +} + +func uint8sValue(i []uint8) Value { + return Value{ + value: i, + vtype: TypeUint8 | TypeArray, + } +} + +func uint8pValue(in *uint8) Value { + if in != nil { + return uint8Value(*in) + } + + return nilValue(TypeUint8) +} + +func uint16Value(i uint16) Value { + return Value{ + vtype: TypeUint16, + numeric: uint64(i), + } +} + +func uint16sValue(i []uint16) Value { + return Value{ + value: i, + vtype: TypeUint16 | TypeArray, + } +} + +func uint16pValue(in *uint16) Value { + if in != nil { + return uint16Value(*in) + } + + return nilValue(TypeUint16) +} + +func uint32Value(i uint32) Value { + return Value{ + vtype: TypeUint32, + numeric: uint64(i), + } +} + +func uint32sValue(i []uint32) Value { + return Value{ + value: i, + vtype: TypeUint32 | TypeArray, + } +} + +func uint32pValue(in *uint32) Value { + if in != nil { + return uint32Value(*in) + } + + return nilValue(TypeUint32) +} + +func uint64Value(i uint64) Value { + return Value{ + vtype: TypeUint64, + numeric: i, + } +} + +func uint64sValue(i []uint64) Value { + return Value{ + value: i, + vtype: TypeUint64 | TypeArray, + } +} + +func uint64pValue(in *uint64) Value { + if in != nil { + return uint64Value(*in) + } + + return nilValue(TypeUint64) +} + +func float32Value(i float32) Value { + return Value{ + vtype: TypeFloat32, + numeric: uint64(math.Float32bits(i)), + } +} + +func float32sValue(i []float32) Value { + return Value{ + value: i, + vtype: TypeFloat32 | TypeArray, + } +} + +func float32pValue(in *float32) Value { + if in != nil { + return float32Value(*in) + } + + return nilValue(TypeFloat32) +} + +func float64Value(i float64) Value { + return Value{ + vtype: TypeFloat64, + numeric: math.Float64bits(i), + } +} + +func float64sValue(i []float64) Value { + return Value{ + value: i, + vtype: TypeFloat64 | TypeArray, + } +} + +func float64pValue(in *float64) Value { + if in != nil { + return float64Value(*in) + } + + return nilValue(TypeFloat64) +} + +func complex64Value(in complex64) Value { + return Value{ + vtype: TypeComplex64, + value: in, + } +} + +func complex64sValue(in []complex64) Value { + return Value{ + vtype: TypeComplex64 | TypeArray, + value: in, + } +} + +func complex64pValue(in *complex64) Value { + if in != nil { + return complex64Value(*in) + } + + return nilValue(TypeComplex64) +} + +func complex128Value(in complex128) Value { + return Value{ + vtype: TypeComplex64, + value: in, + } +} + +func complex128sValue(in []complex128) Value { + return Value{ + vtype: TypeComplex128 | TypeArray, + value: in, + } +} + +func complex128pValue(in *complex128) Value { + if in != nil { + return complex128Value(*in) + } + + return nilValue(TypeComplex128) +} + +func uintptrValue(in uintptr) Value { + return Value{ + vtype: TypeUintptr, + value: in, + } +} + +func uintptrsValue(in []uintptr) Value { + return Value{ + vtype: TypeUintptr | TypeArray, + value: in, + } +} + +func uintptrpValue(in *uintptr) Value { + if in != nil { + return uintptrValue(*in) + } + + return nilValue(TypeUintptr) +} + +func bytesValue(in []byte) Value { + return Value{ + vtype: TypeBinary, + value: in, + } +} + +func durationValue(in time.Duration) Value { + return Value{ + vtype: TypeDuration, + value: in, + } +} + +func durationsValue(in []time.Duration) Value { + return Value{ + vtype: TypeDuration | TypeArray, + value: in, + } +} + +func durationpValue(in *time.Duration) Value { + if in != nil { + return durationValue(*in) + } + + return nilValue(TypeDuration) +} + +func timeValue(in time.Time) Value { + return formatTimeValue(time.RFC3339, in) +} + +func timesValue(in []time.Time) Value { + return formatTimesValue(time.RFC3339, in) +} + +func timepValue(in *time.Time) Value { + return formatTimepValue(time.RFC3339, in) +} + +func formatTimeValue(format string, in time.Time) Value { + return Value{ + vtype: TypeTime, + value: in, + stringly: format, + } +} + +func formatTimesValue(format string, in []time.Time) Value { + return Value{ + vtype: TypeTime | TypeArray, + value: in, + stringly: format, + } +} + +func formatTimepValue(format string, in *time.Time) Value { + if in != nil { + return formatTimeValue(format, *in) + } + + return nilValue(TypeTime) +} + +func errorValue(in error) Value { + if in != nil { + return Value{ + vtype: TypeError, + value: in, + } + } + + return nilValue(TypeError) +} + +func errorsValue(in []error) Value { + return Value{ + vtype: TypeError | TypeArray, + value: in, + } +} diff --git a/global.go b/global.go index 03f9a94..369d9be 100644 --- a/global.go +++ b/global.go @@ -2,18 +2,27 @@ package log import ( "context" + "io" + + "gitoa.ru/go-4devs/log/field" + "gitoa.ru/go-4devs/log/level" ) //nolint:gochecknoglobals -var global = With(New(), WithLevel(LevelDebug)) +var global = With(New(), + WithCaller("caller", 1, false), + WithLevel("level", level.Debug), + WithExit(level.Alert), + WithPanic(level.Emergency), +) // SetLogger sets global used logger. This function is not thread-safe. func SetLogger(l Logger) { global = l } -// GetLogger return global logger. -func GetLogger() Logger { +// Log return global logger. +func Log() Logger { return global } @@ -87,43 +96,83 @@ func Panicln(args ...interface{}) { global.Panicln(args...) } -// EmergKV log by emergency level and key-values. -func EmergKV(ctx context.Context, msg string, args ...interface{}) { +// EmergKVs sugared log by emergency level and key-values. +func EmergKVs(ctx context.Context, msg string, args ...interface{}) { + global.EmergKVs(ctx, msg, args...) +} + +// AlertKVs sugared log by alert level and key-values. +func AlertKVs(ctx context.Context, msg string, args ...interface{}) { + global.AlertKVs(ctx, msg, args...) +} + +// CritKVs sugared log by critcal level and key-values. +func CritKVs(ctx context.Context, msg string, args ...interface{}) { + global.CritKVs(ctx, msg, args...) +} + +// ErrKVs sugared log by error level and key-values. +func ErrKVs(ctx context.Context, msg string, args ...interface{}) { + global.ErrKVs(ctx, msg, args...) +} + +// WarnKVs sugared log by warning level and key-values. +func WarnKVs(ctx context.Context, msg string, args ...interface{}) { + global.WarnKVs(ctx, msg, args...) +} + +// NoticeKVs sugared log by notice level and key-values. +func NoticeKVs(ctx context.Context, msg string, args ...interface{}) { + global.NoticeKVs(ctx, msg, args...) +} + +// InfoKVs sugared log by info level and key-values. +func InfoKVs(ctx context.Context, msg string, args ...interface{}) { + global.InfoKVs(ctx, msg, args...) +} + +// DebugKVs sugared log by debug level and key-values. +func DebugKVs(ctx context.Context, msg string, args ...interface{}) { + global.DebugKVs(ctx, msg, args...) +} + +// EmergKV log by emergency level and key-values. +func EmergKV(ctx context.Context, msg string, args ...field.Field) { global.EmergKV(ctx, msg, args...) } // AlertKV log by alert level and key-values. -func AlertKV(ctx context.Context, msg string, args ...interface{}) { +func AlertKV(ctx context.Context, msg string, args ...field.Field) { global.AlertKV(ctx, msg, args...) } // CritKV log by critcal level and key-values. -func CritKV(ctx context.Context, msg string, args ...interface{}) { +func CritKV(ctx context.Context, msg string, args ...field.Field) { global.CritKV(ctx, msg, args...) } // ErrKV log by error level and key-values. -func ErrKV(ctx context.Context, msg string, args ...interface{}) { +func ErrKV(ctx context.Context, msg string, args ...field.Field) { global.ErrKV(ctx, msg, args...) } // WarnKV log by warning level and key-values. -func WarnKV(ctx context.Context, msg string, args ...interface{}) { +func WarnKV(ctx context.Context, msg string, args ...field.Field) { global.WarnKV(ctx, msg, args...) } // NoticeKV log by notice level and key-values. -func NoticeKV(ctx context.Context, msg string, args ...interface{}) { +func NoticeKV(ctx context.Context, msg string, args ...field.Field) { global.NoticeKV(ctx, msg, args...) } // InfoKV log by info level and key-values. -func InfoKV(ctx context.Context, msg string, args ...interface{}) { +func InfoKV(ctx context.Context, msg string, args ...field.Field) { global.InfoKV(ctx, msg, args...) } // DebugKV log by debug level and key-values. -func DebugKV(ctx context.Context, msg string, args ...interface{}) { +func DebugKV(ctx context.Context, msg string, args ...field.Field) { global.DebugKV(ctx, msg, args...) } @@ -181,3 +230,7 @@ func Fatalf(format string, args ...interface{}) { func Panicf(format string, args ...interface{}) { global.Panicf(format, args...) } + +func Writer(ctx context.Context, level level.Level) io.Writer { + return global.Writer(ctx, level) +} diff --git a/go.mod b/go.mod index 1ddc29f..f5b814a 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,10 @@ module gitoa.ru/go-4devs/log -go 1.14 +go 1.15 + +require ( + github.com/sirupsen/logrus v1.7.0 + go.opentelemetry.io/otel v0.13.0 + go.opentelemetry.io/otel/sdk v0.13.0 + go.uber.org/zap v1.16.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a5cf428 --- /dev/null +++ b/go.sum @@ -0,0 +1,76 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/sketches-go v0.0.1/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.opentelemetry.io/otel v0.13.0 h1:2isEnyzjjJZq6r2EKMsFj4TxiQiexsM04AVhwbR/oBA= +go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY= +go.opentelemetry.io/otel/sdk v0.13.0 h1:4VCfpKamZ8GtnepXxMRurSpHpMKkcxhtO33z1S4rGDQ= +go.opentelemetry.io/otel/sdk v0.13.0/go.mod h1:dKvLH8Uu8LcEPlSAUsfW7kMGaJBhk/1NYvpPZ6wIMbU= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/handler/logrus/logger.go b/handler/logrus/logger.go new file mode 100644 index 0000000..6d581ba --- /dev/null +++ b/handler/logrus/logger.go @@ -0,0 +1,90 @@ +package logrus + +import ( + "context" + + "github.com/sirupsen/logrus" + "gitoa.ru/go-4devs/log" + "gitoa.ru/go-4devs/log/entry" + "gitoa.ru/go-4devs/log/level" +) + +// Option configure logger. +type Option func(*logger) + +// WithLevel sets callback level to log level. +func WithLevel(level level.Level, c func(*logrus.Entry, string)) Option { + return func(l *logger) { + l.levels[level] = c + } +} + +// WithLogrus sets logrus logger. +func WithLogrus(logrus *logrus.Logger) Option { + return func(l *logger) { + l.logrus = logrus + } +} + +// New create new logrus handler. +func New(opts ...Option) log.Logger { + log := logger{ + logrus: logrus.StandardLogger(), + levels: map[level.Level]func(*logrus.Entry, string){ + level.Emergency: panicLog, + level.Alert: fatalLog, + level.Critical: errorLog, + level.Error: errorLog, + level.Warning: warnLog, + level.Notice: infoLog, + level.Info: infoLog, + level.Debug: debugLog, + }, + } + + for _, o := range opts { + o(&log) + } + + return log.log +} + +type logger struct { + levels map[level.Level]func(l *logrus.Entry, msg string) + logrus *logrus.Logger +} + +func (l *logger) log(ctx context.Context, e *entry.Entry) (int, error) { + lrgFields := make(logrus.Fields, e.Fields().Len()) + for _, field := range e.Fields() { + lrgFields[string(field.Key())] = field.AsInterface() + } + + l.levels[e.Level()](l.logrus.WithFields(lrgFields), e.Message()) + + return 0, nil +} + +func panicLog(e *logrus.Entry, msg string) { + e.Panic(msg) +} + +func fatalLog(e *logrus.Entry, msg string) { + e.Fatal(msg) +} + +func errorLog(e *logrus.Entry, msg string) { + e.Error(msg) +} + +func warnLog(e *logrus.Entry, msg string) { + e.Warn(msg) +} + +func infoLog(e *logrus.Entry, msg string) { + e.Info(msg) +} + +func debugLog(e *logrus.Entry, msg string) { + e.Debug(msg) +} diff --git a/handler/logrus/logger_test.go b/handler/logrus/logger_test.go new file mode 100644 index 0000000..39da4a8 --- /dev/null +++ b/handler/logrus/logger_test.go @@ -0,0 +1,36 @@ +package logrus_test + +import ( + "bytes" + "context" + "strings" + "testing" + + lgr "github.com/sirupsen/logrus" + "gitoa.ru/go-4devs/log/entry" + "gitoa.ru/go-4devs/log/handler/logrus" + "gitoa.ru/go-4devs/log/level" +) + +func TestNew(t *testing.T) { + ctx := context.Background() + buf := &bytes.Buffer{} + + lgrus := lgr.New() + lgrus.SetLevel(lgr.DebugLevel) + lgrus.SetOutput(buf) + lgrus.SetFormatter(&lgr.TextFormatter{ + DisableTimestamp: true, + }) + + handler := logrus.New(logrus.WithLogrus(lgrus)) + expect := "level=info msg=\"handle logrus message\"\n" + + if _, err := handler(ctx, entry.New(entry.WithLevel(level.Info), entry.WithMessage("handle logrus message"))); err != nil { + t.Error(err) + } + + if !strings.HasSuffix(buf.String(), expect) { + t.Errorf("invalid suffix\n got: %s\nexpect:%s\n", buf.String(), expect) + } +} diff --git a/handler/otel/helpers.go b/handler/otel/helpers.go new file mode 100644 index 0000000..a57b731 --- /dev/null +++ b/handler/otel/helpers.go @@ -0,0 +1,56 @@ +package otel + +import ( + "context" + + "gitoa.ru/go-4devs/log/entry" + "gitoa.ru/go-4devs/log/level" + "go.opentelemetry.io/otel/api/trace" + "go.opentelemetry.io/otel/label" +) + +const ( + fieldSeverityNumber = "SeverityNumber" + fieldSeverityText = "SeverityText" + levelFields = 2 +) + +func levels(lvl level.Level) Level { + switch lvl { + case level.Emergency: + return levelError3 + case level.Alert: + return levelFatal + case level.Critical: + return levelError2 + case level.Error: + return levelError + case level.Warning: + return levelWarn + case level.Notice: + return levelInfo2 + case level.Info: + return levelInfo + case level.Debug: + return levelDebug + } + + return 0 +} + +func addEvent(ctx context.Context, e *entry.Entry) { + span := trace.SpanFromContext(ctx) + attrs := make([]label.KeyValue, 0, e.Fields().Len()+levelFields) + + lvl := levels(e.Level()) + attrs = append(attrs, + label.String(fieldSeverityText, lvl.String()), + label.Int(fieldSeverityNumber, int(lvl)), + ) + + for _, field := range e.Fields() { + attrs = append(attrs, label.String(string(field.Key()), field.Value().String())) + } + + span.AddEvent(ctx, e.Message(), attrs...) +} diff --git a/handler/otel/level.go b/handler/otel/level.go new file mode 100644 index 0000000..7d0f0fd --- /dev/null +++ b/handler/otel/level.go @@ -0,0 +1,16 @@ +package otel + +//go:generate stringer -type=Level -linecomment -output=level_string.go + +type Level int + +const ( + levelDebug Level = 5 // DEBUG + levelInfo Level = 9 // INFO + levelInfo2 Level = 10 // INFO2 + levelWarn Level = 13 // WARN + levelError Level = 17 // ERROR + levelError2 Level = 18 // ERROR2 + levelError3 Level = 19 // ERROR3 + levelFatal Level = 21 // FATAL +) diff --git a/handler/otel/level_string.go b/handler/otel/level_string.go new file mode 100644 index 0000000..0a393c2 --- /dev/null +++ b/handler/otel/level_string.go @@ -0,0 +1,51 @@ +// Code generated by "stringer -type=Level -linecomment -output=level_string.go"; DO NOT EDIT. + +package otel + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[levelDebug-5] + _ = x[levelInfo-9] + _ = x[levelInfo2-10] + _ = x[levelWarn-13] + _ = x[levelError-17] + _ = x[levelError2-18] + _ = x[levelError3-19] + _ = x[levelFatal-21] +} + +const ( + _Level_name_0 = "DEBUG" + _Level_name_1 = "INFOINFO2" + _Level_name_2 = "WARN" + _Level_name_3 = "ERRORERROR2ERROR3" + _Level_name_4 = "FATAL" +) + +var ( + _Level_index_1 = [...]uint8{0, 4, 9} + _Level_index_3 = [...]uint8{0, 5, 11, 17} +) + +func (i Level) String() string { + switch { + case i == 5: + return _Level_name_0 + case 9 <= i && i <= 10: + i -= 9 + return _Level_name_1[_Level_index_1[i]:_Level_index_1[i+1]] + case i == 13: + return _Level_name_2 + case 17 <= i && i <= 19: + i -= 17 + return _Level_name_3[_Level_index_3[i]:_Level_index_3[i+1]] + case i == 21: + return _Level_name_4 + default: + return "Level(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/handler/otel/logger.go b/handler/otel/logger.go new file mode 100644 index 0000000..ba256f1 --- /dev/null +++ b/handler/otel/logger.go @@ -0,0 +1,16 @@ +package otel + +import ( + "context" + + "gitoa.ru/go-4devs/log" + "gitoa.ru/go-4devs/log/entry" +) + +func New() log.Logger { + return func(ctx context.Context, e *entry.Entry) (int, error) { + addEvent(ctx, e) + + return 0, nil + } +} diff --git a/handler/otel/middleware.go b/handler/otel/middleware.go new file mode 100644 index 0000000..7b7be1d --- /dev/null +++ b/handler/otel/middleware.go @@ -0,0 +1,16 @@ +package otel + +import ( + "context" + + "gitoa.ru/go-4devs/log" + "gitoa.ru/go-4devs/log/entry" +) + +func Middleware() log.Middleware { + return func(ctx context.Context, e *entry.Entry, handler log.Logger) (int, error) { + addEvent(ctx, e) + + return handler(ctx, e) + } +} diff --git a/handler/zap/logger.go b/handler/zap/logger.go new file mode 100644 index 0000000..51d1eb3 --- /dev/null +++ b/handler/zap/logger.go @@ -0,0 +1,95 @@ +package zap + +import ( + "context" + + "gitoa.ru/go-4devs/log" + "gitoa.ru/go-4devs/log/entry" + "gitoa.ru/go-4devs/log/level" + "go.uber.org/zap" +) + +// Option configure logger. +type Option func(*logger) + +// WithLevel sets level logged message. +func WithLevel(level level.Level, f func(z *zap.Logger, msg string, fields ...zap.Field)) Option { + return func(l *logger) { + l.levels[level] = f + } +} + +// WithZap sets zap logger. +func WithZap(z *zap.Logger) Option { + return func(l *logger) { + l.zap = z + } +} + +// New create handler by zap logger. +func New(opts ...Option) log.Logger { + z, err := zap.NewDevelopment() + if err != nil { + panic(err) + } + + log := logger{ + zap: z, + levels: map[level.Level]func(z *zap.Logger, msg string, fields ...zap.Field){ + level.Emergency: fatalLog, + level.Alert: panicLog, + level.Critical: errorLog, + level.Error: errorLog, + level.Warning: warnLog, + level.Notice: infoLog, + level.Info: infoLog, + level.Debug: debugLog, + }, + } + + for _, opt := range opts { + opt(&log) + } + + return log.log +} + +type logger struct { + zap *zap.Logger + levels map[level.Level]func(z *zap.Logger, msg string, fields ...zap.Field) +} + +func (l *logger) log(ctx context.Context, e *entry.Entry) (int, error) { + zf := make([]zap.Field, e.Fields().Len()) + for i, field := range e.Fields() { + zf[i] = zap.Any(string(field.Key()), field.AsInterface()) + } + + l.levels[e.Level()](l.zap, e.Message(), zf...) + + return 0, nil +} + +func panicLog(z *zap.Logger, msg string, fields ...zap.Field) { + z.Panic(msg, fields...) +} + +func fatalLog(z *zap.Logger, msg string, fields ...zap.Field) { + z.Fatal(msg, fields...) +} + +func errorLog(z *zap.Logger, msg string, fields ...zap.Field) { + z.Error(msg, fields...) +} + +func warnLog(z *zap.Logger, msg string, fields ...zap.Field) { + z.Warn(msg, fields...) +} + +func infoLog(z *zap.Logger, msg string, fields ...zap.Field) { + z.Info(msg, fields...) +} + +func debugLog(z *zap.Logger, msg string, fields ...zap.Field) { + z.Debug(msg, fields...) +} diff --git a/handler/zap/logger_test.go b/handler/zap/logger_test.go new file mode 100644 index 0000000..66f131a --- /dev/null +++ b/handler/zap/logger_test.go @@ -0,0 +1,41 @@ +package zap_test + +import ( + "bytes" + "context" + "testing" + + "gitoa.ru/go-4devs/log/entry" + "gitoa.ru/go-4devs/log/field" + zlog "gitoa.ru/go-4devs/log/handler/zap" + "gitoa.ru/go-4devs/log/level" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func TestNew(t *testing.T) { + ctx := context.Background() + buf := &bytes.Buffer{} + core := zapcore.NewCore(zapcore.NewJSONEncoder(zapcore.EncoderConfig{ + MessageKey: "msg", + LevelKey: "level", + NameKey: "logger", + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + }), zapcore.AddSync(buf), zapcore.DebugLevel) + logger := zlog.New(zlog.WithZap(zap.New(core))) + expect := `{"level":"info","msg":"handle zap message","env":"test"}` + "\n" + + if _, err := logger(ctx, entry.New( + entry.WithFields(field.String("env", "test")), + entry.WithLevel(level.Notice), + entry.WithMessage("handle zap message"), + )); err != nil { + t.Error(err) + } + + if buf.String() != expect { + t.Errorf("invalid message\n got: %s\nexpect:%s\n", buf.String(), expect) + } +} diff --git a/level.go b/level.go deleted file mode 100644 index de8c03e..0000000 --- a/level.go +++ /dev/null @@ -1,22 +0,0 @@ -package log - -//go:generate stringer -type=Level -linecomment - -// Level log. -type Level uint8 - -// awailable log levels. -const ( - LevelEmergency Level = iota // emergency - LevelAlert // alert - LevelCritical // critical - LevelError // error - LevelWarning // warning - LevelNotice // notice - LevelInfo // info - LevelDebug // debug -) - -func (l Level) MarshalJSON() ([]byte, error) { - return []byte("\"" + l.String() + "\""), nil -} diff --git a/level/level.go b/level/level.go new file mode 100644 index 0000000..6ce25a7 --- /dev/null +++ b/level/level.go @@ -0,0 +1,73 @@ +package level + +import ( + "encoding/json" + "strings" +) + +//go:generate stringer -type=Level -linecomment + +var ( + _ json.Marshaler = Level(0) + _ json.Unmarshaler = (*Level)(nil) +) + +// Level log. +type Level uint32 + +// available log levels. +const ( + Emergency Level = iota // emergency + Alert // alert + Critical // critical + Error // error + Warning // warning + Notice // notice + Info // info + Debug // debug +) + +func (l Level) MarshalJSON() ([]byte, error) { + return json.Marshal(l.String()) +} + +func (l Level) Is(level Level) bool { + return level == l +} + +func (l Level) Enabled(level Level) bool { + return l <= level +} + +func (l *Level) UnmarshalJSON(in []byte) error { + var v string + if err := json.Unmarshal(in, &v); err != nil { + return err + } + + lvl := Parse(v) + *l = lvl + + return nil +} + +func Parse(lvl string) Level { + switch strings.ToLower(lvl) { + case "debug", "Debug", "DEBUG": + return Debug + case "info", "Info", "INFO": + return Info + case "notice", "Notice", "NOTICE": + return Notice + case "warning", "Warning", "WARNING": + return Warning + case "error", "Error", "ERROR": + return Error + case "critical", "Critical", "CRITICAL": + return Critical + case "alert", "Alert", "ALERT": + return Alert + default: + return Emergency + } +} diff --git a/level_string.go b/level/level_string.go similarity index 76% rename from level_string.go rename to level/level_string.go index def99d1..e384efa 100644 --- a/level_string.go +++ b/level/level_string.go @@ -1,6 +1,6 @@ // Code generated by "stringer -type=Level -linecomment"; DO NOT EDIT. -package log +package level import "strconv" @@ -8,14 +8,14 @@ func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} - _ = x[LevelEmergency-0] - _ = x[LevelAlert-1] - _ = x[LevelCritical-2] - _ = x[LevelError-3] - _ = x[LevelWarning-4] - _ = x[LevelNotice-5] - _ = x[LevelInfo-6] - _ = x[LevelDebug-7] + _ = x[Emergency-0] + _ = x[Alert-1] + _ = x[Critical-2] + _ = x[Error-3] + _ = x[Warning-4] + _ = x[Notice-5] + _ = x[Info-6] + _ = x[Debug-7] } const _Level_name = "emergencyalertcriticalerrorwarningnoticeinfodebug" diff --git a/logger.go b/logger.go index 536f740..0a74cbd 100644 --- a/logger.go +++ b/logger.go @@ -2,39 +2,83 @@ package log import ( "context" + "errors" "fmt" + "io" + "os" + + "gitoa.ru/go-4devs/log/entry" + "gitoa.ru/go-4devs/log/field" + "gitoa.ru/go-4devs/log/level" +) + +var _ io.Writer = (Logger)(nil) + +var ( + ErrIgnoredKey = errors.New("ignored key without a value") + ErrNonStringKeys = errors.New("ignored key-value pairs with non-string keys") ) +func writeOutput(_ int, err error) { + if err != nil { + fmt.Fprint(os.Stderr, err) + } +} + // Logger logged message. -type Logger func(ctx context.Context, level Level, msg string, fields Fields) +type Logger func(ctx context.Context, entry *entry.Entry) (int, error) -func (l Logger) log(ctx context.Context, level Level, args ...interface{}) { - l(ctx, level, fmt.Sprint(args...), nil) +func (l Logger) log(ctx context.Context, level level.Level, args ...interface{}) { + writeOutput(l.write(ctx, level, fmt.Sprint(args...))) } -func (l Logger) logKV(ctx context.Context, level Level, msg string, args ...interface{}) { - l(ctx, level, msg, l.kv(ctx, args...)) +func (l Logger) Write(in []byte) (int, error) { + return l.write(context.Background(), level.Info, string(in)) } -func (l Logger) logf(ctx context.Context, level Level, format string, args ...interface{}) { - l(ctx, level, fmt.Sprintf(format, args...), nil) +func (l Logger) write(ctx context.Context, level level.Level, msg string, fields ...field.Field) (int, error) { + e := entry.Get() + + defer func() { + entry.Put(e) + }() + + return l(ctx, e.SetLevel(level).SetMessage(msg).Add(fields...)) } -func (l Logger) logln(ctx context.Context, level Level, args ...interface{}) { - l(ctx, level, fmt.Sprintln(args...), nil) +func (l Logger) logKVs(ctx context.Context, level level.Level, msg string, args ...interface{}) { + writeOutput(l.write(ctx, level, msg, l.kv(ctx, args...)...)) } -func (l Logger) kv(ctx context.Context, args ...interface{}) []Field { - fields := make([]Field, 0, len(args)) +func (l Logger) logKV(ctx context.Context, level level.Level, msg string, fields ...field.Field) { + writeOutput(l.write(ctx, level, msg, fields...)) +} + +func (l Logger) logf(ctx context.Context, level level.Level, format string, args ...interface{}) { + writeOutput(l.write(ctx, level, fmt.Sprintf(format, args...))) +} + +func (l Logger) logln(ctx context.Context, level level.Level, args ...interface{}) { + writeOutput(l.write(ctx, level, fmt.Sprintln(args...))) +} + +func (l Logger) kv(ctx context.Context, args ...interface{}) field.Fields { + e := entry.Get() + + defer func() { + entry.Put(e) + }() for i := 0; i < len(args); i++ { - if f, ok := args[i].(Field); ok { - fields = append(fields, f) + if f, ok := args[i].(field.Field); ok { + e = e.Add(f) + continue } if i == len(args)-1 { - l(ctx, LevelCritical, fmt.Sprint("Ignored key without a value.", args[i]), fields) + l.logKV(ctx, level.Critical, fmt.Sprint("Ignored key without a value.", args[i]), e.Fields()...) + break } @@ -42,14 +86,15 @@ func (l Logger) kv(ctx context.Context, args ...interface{}) []Field { key, val := args[i-1], args[i] if keyStr, ok := key.(string); ok { - fields = append(fields, Field{Key: keyStr, Value: val}) + e = e.AddAny(keyStr, val) + continue } - l(ctx, LevelCritical, fmt.Sprint("Ignored key-value pairs with non-string keys.", key, val), fields) + l.logKV(ctx, level.Critical, fmt.Sprint("Ignored key-value pairs with non-string keys.", key, val), e.Fields()...) } - return fields + return e.Fields() } // With adds middlewares to logger. @@ -59,165 +104,225 @@ func (l Logger) With(mw ...Middleware) Logger { // Emerg log by emergency level. func (l Logger) Emerg(ctx context.Context, args ...interface{}) { - l.log(ctx, LevelEmergency, args...) + l.log(ctx, level.Emergency, args...) } // Alert log by alert level. func (l Logger) Alert(ctx context.Context, args ...interface{}) { - l.log(ctx, LevelAlert, args...) + l.log(ctx, level.Alert, args...) } // Crit log by critical level. func (l Logger) Crit(ctx context.Context, args ...interface{}) { - l.log(ctx, LevelCritical, args...) + l.log(ctx, level.Critical, args...) } // Err log by error level. func (l Logger) Err(ctx context.Context, args ...interface{}) { - l.log(ctx, LevelError, args...) + l.log(ctx, level.Error, args...) } // Warn log by warning level. func (l Logger) Warn(ctx context.Context, args ...interface{}) { - l.log(ctx, LevelWarning, args...) + l.log(ctx, level.Warning, args...) } // Notice log by notice level. func (l Logger) Notice(ctx context.Context, args ...interface{}) { - l.log(ctx, LevelNotice, args...) + l.log(ctx, level.Notice, args...) } // Info log by info level. func (l Logger) Info(ctx context.Context, args ...interface{}) { - l.log(ctx, LevelInfo, args...) + l.log(ctx, level.Info, args...) } // Debug log by debug level. func (l Logger) Debug(ctx context.Context, args ...interface{}) { - l.log(ctx, LevelDebug, args...) + l.log(ctx, level.Debug, args...) } // Print log by info level and arguments. func (l Logger) Print(args ...interface{}) { - l.log(context.Background(), LevelInfo, args...) + l.log(context.Background(), level.Info, args...) } // Fatal log by alert level and arguments. func (l Logger) Fatal(args ...interface{}) { - l.log(context.Background(), LevelAlert, args...) + l.log(context.Background(), level.Alert, args...) } // Panic log by emergency level and arguments. func (l Logger) Panic(args ...interface{}) { - l.log(context.Background(), LevelEmergency, args...) + l.log(context.Background(), level.Emergency, args...) } // Println log by info level and arguments. func (l Logger) Println(args ...interface{}) { - l.logln(context.Background(), LevelInfo, args...) + l.logln(context.Background(), level.Info, args...) } // Fatalln log by alert level and arguments. func (l Logger) Fatalln(args ...interface{}) { - l.logln(context.Background(), LevelAlert, args...) + l.logln(context.Background(), level.Alert, args...) } // Panicln log by emergency level and arguments. func (l Logger) Panicln(args ...interface{}) { - l.logln(context.Background(), LevelEmergency, args...) + l.logln(context.Background(), level.Emergency, args...) +} + +// EmergKVs sugared log by emergency level and key-values. +func (l Logger) EmergKVs(ctx context.Context, msg string, args ...interface{}) { + l.logKVs(ctx, level.Emergency, msg, args...) +} + +// AlertKVs sugared log by alert level and key-values. +func (l Logger) AlertKVs(ctx context.Context, msg string, args ...interface{}) { + l.logKVs(ctx, level.Alert, msg, args...) +} + +// CritKVs sugared log by critcal level and key-values. +func (l Logger) CritKVs(ctx context.Context, msg string, args ...interface{}) { + l.logKVs(ctx, level.Critical, msg, args...) +} + +// ErrKVs sugared log by error level and key-values. +func (l Logger) ErrKVs(ctx context.Context, msg string, args ...interface{}) { + l.logKVs(ctx, level.Error, msg, args...) +} + +// WarnKVs sugared log by warning level and key-values. +func (l Logger) WarnKVs(ctx context.Context, msg string, args ...interface{}) { + l.logKVs(ctx, level.Warning, msg, args...) +} + +// NoticeKVs sugared log by notice level and key-values. +func (l Logger) NoticeKVs(ctx context.Context, msg string, args ...interface{}) { + l.logKVs(ctx, level.Notice, msg, args...) +} + +// InfoKVs sugared log by info level and key-values. +func (l Logger) InfoKVs(ctx context.Context, msg string, args ...interface{}) { + l.logKVs(ctx, level.Info, msg, args...) +} + +// DebugKVs sugared log by debug level and key-values. +func (l Logger) DebugKVs(ctx context.Context, msg string, args ...interface{}) { + l.logKVs(ctx, level.Debug, msg, args...) } // EmergKV log by emergency level and key-values. -func (l Logger) EmergKV(ctx context.Context, msg string, args ...interface{}) { - l.logKV(ctx, LevelEmergency, msg, args...) +func (l Logger) EmergKV(ctx context.Context, msg string, args ...field.Field) { + l.logKV(ctx, level.Emergency, msg, args...) } // AlertKV log by alert level and key-values. -func (l Logger) AlertKV(ctx context.Context, msg string, args ...interface{}) { - l.logKV(ctx, LevelAlert, msg, args...) +func (l Logger) AlertKV(ctx context.Context, msg string, args ...field.Field) { + l.logKV(ctx, level.Alert, msg, args...) } // CritKV log by critcal level and key-values. -func (l Logger) CritKV(ctx context.Context, msg string, args ...interface{}) { - l.logKV(ctx, LevelCritical, msg, args...) +func (l Logger) CritKV(ctx context.Context, msg string, args ...field.Field) { + l.logKV(ctx, level.Critical, msg, args...) } // ErrKV log by error level and key-values. -func (l Logger) ErrKV(ctx context.Context, msg string, args ...interface{}) { - l.logKV(ctx, LevelError, msg, args...) +func (l Logger) ErrKV(ctx context.Context, msg string, args ...field.Field) { + l.logKV(ctx, level.Error, msg, args...) } // WarnKV log by warning level and key-values. -func (l Logger) WarnKV(ctx context.Context, msg string, args ...interface{}) { - l.logKV(ctx, LevelWarning, msg, args...) +func (l Logger) WarnKV(ctx context.Context, msg string, args ...field.Field) { + l.logKV(ctx, level.Warning, msg, args...) } // NoticeKV log by notice level and key-values. -func (l Logger) NoticeKV(ctx context.Context, msg string, args ...interface{}) { - l.logKV(ctx, LevelNotice, msg, args...) +func (l Logger) NoticeKV(ctx context.Context, msg string, args ...field.Field) { + l.logKV(ctx, level.Notice, msg, args...) } // InfoKV log by info level and key-values. -func (l Logger) InfoKV(ctx context.Context, msg string, args ...interface{}) { - l.logKV(ctx, LevelInfo, msg, args...) +func (l Logger) InfoKV(ctx context.Context, msg string, args ...field.Field) { + l.logKV(ctx, level.Info, msg, args...) } // DebugKV log by debug level and key-values. -func (l Logger) DebugKV(ctx context.Context, msg string, args ...interface{}) { - l.logKV(ctx, LevelDebug, msg, args...) +func (l Logger) DebugKV(ctx context.Context, msg string, args ...field.Field) { + l.logKV(ctx, level.Debug, msg, args...) } // Emergf log by emergency level by format and arguments. func (l Logger) Emergf(ctx context.Context, format string, args ...interface{}) { - l.logf(ctx, LevelEmergency, format, args...) + l.logf(ctx, level.Emergency, format, args...) } // Alertf log by alert level by format and arguments. func (l Logger) Alertf(ctx context.Context, format string, args ...interface{}) { - l.logf(ctx, LevelAlert, format, args...) + l.logf(ctx, level.Alert, format, args...) } // Critf log by critical level by format and arguments. func (l Logger) Critf(ctx context.Context, format string, args ...interface{}) { - l.logf(ctx, LevelCritical, format, args...) + l.logf(ctx, level.Critical, format, args...) } // Errf log by error level by format and arguments. func (l Logger) Errf(ctx context.Context, format string, args ...interface{}) { - l.logf(ctx, LevelError, format, args...) + l.logf(ctx, level.Error, format, args...) } // Warnf log by warning level by format and arguments. func (l Logger) Warnf(ctx context.Context, format string, args ...interface{}) { - l.logf(ctx, LevelWarning, format, args...) + l.logf(ctx, level.Warning, format, args...) } // Noticef log by notice level by format and arguments. func (l Logger) Noticef(ctx context.Context, format string, args ...interface{}) { - l.logf(ctx, LevelNotice, format, args...) + l.logf(ctx, level.Notice, format, args...) } // Infof log by info level by format and arguments. func (l Logger) Infof(ctx context.Context, format string, args ...interface{}) { - l.logf(ctx, LevelInfo, format, args...) + l.logf(ctx, level.Info, format, args...) } // Debugf log by debug level by format and arguments. func (l Logger) Debugf(ctx context.Context, format string, args ...interface{}) { - l.logf(ctx, LevelDebug, format, args...) + l.logf(ctx, level.Debug, format, args...) } // Printf log by info level by format and arguments without context. func (l Logger) Printf(format string, args ...interface{}) { - l.logf(context.Background(), LevelInfo, format, args...) + l.logf(context.Background(), level.Info, format, args...) } // Fatalf log by alert level by format and arguments without context. func (l Logger) Fatalf(format string, args ...interface{}) { - l.logf(context.Background(), LevelAlert, format, args...) + l.logf(context.Background(), level.Alert, format, args...) } // Panicf log by emergency level and arguments without context. func (l Logger) Panicf(format string, args ...interface{}) { - l.logf(context.Background(), LevelEmergency, format, args...) + l.logf(context.Background(), level.Emergency, format, args...) +} + +func (l Logger) Writer(ctx context.Context, level level.Level, fields ...field.Field) io.Writer { + return writer{ + ctx: ctx, + level: level, + Logger: l, + fields: fields, + } +} + +type writer struct { + ctx context.Context + level level.Level + fields []field.Field + Logger +} + +func (w writer) Write(in []byte) (int, error) { + return w.write(w.ctx, w.level, string(in), w.fields...) } diff --git a/logger_example_caller_test.go b/logger_example_caller_test.go new file mode 100644 index 0000000..49e99f3 --- /dev/null +++ b/logger_example_caller_test.go @@ -0,0 +1,20 @@ +package log_test + +import ( + "gitoa.ru/go-4devs/log" + "gitoa.ru/go-4devs/log/level" +) + +func ExampleNew_withCaller() { + logger := log.With( + log.New(log.WithStdout()), + log.WithLevel("level", level.Debug), + log.WithCaller("caller", 2, false), + ) + logger.Err(ctx, "same error message") + logger.InfoKVs(ctx, "same info message", "api-version", 0.1) + + // Output: + // msg="same error message" level=error caller=logger_example_caller_test.go:14 + // msg="same info message" api-version=0.1 level=info caller=logger_example_caller_test.go:15 +} diff --git a/logger_example_logrus_test.go b/logger_example_logrus_test.go new file mode 100644 index 0000000..558048c --- /dev/null +++ b/logger_example_logrus_test.go @@ -0,0 +1,21 @@ +package log_test + +import ( + "io" + + "gitoa.ru/go-4devs/log/field" + "gitoa.ru/go-4devs/log/handler/zap" + uzap "go.uber.org/zap" +) + +func ExampleNew_zapHandler() { + log := zap.New(zap.WithZap(uzap.NewExample())) + log.Err(ctx, "log zap") + log.ErrKV(ctx, "log zap kv", field.Int("int", 42)) + log.ErrKVs(ctx, "log zap kv sugar", "err", io.EOF) + + // Output: + // {"level":"error","msg":"log zap"} + // {"level":"error","msg":"log zap kv","int":42} + // {"level":"error","msg":"log zap kv sugar","err":"EOF"} +} diff --git a/logger_example_test.go b/logger_example_test.go index 8d810df..4b097de 100644 --- a/logger_example_test.go +++ b/logger_example_test.go @@ -3,15 +3,23 @@ package log_test import ( "context" "fmt" - std "log" "os" + "sync/atomic" "gitoa.ru/go-4devs/log" + "gitoa.ru/go-4devs/log/entry" + "gitoa.ru/go-4devs/log/field" + "gitoa.ru/go-4devs/log/level" ) //nolint:gochecknoglobals var ctx = context.Background() +func setStdout() { + // set stout for example by default stderror + log.SetLogger(log.New(log.WithStdout()).With(log.WithLevel("level", level.Debug))) +} + func ExampleNew() { logger := log.New(log.WithStdout()) logger.Info(ctx, "same message") @@ -19,16 +27,14 @@ func ExampleNew() { } func ExampleInfo() { - std.SetOutput(os.Stdout) - std.SetFlags(0) + setStdout() log.Info(ctx, "same message") // Output: msg="same message" level=info } func ExampleErrKV() { - std.SetOutput(os.Stdout) - std.SetFlags(0) - log.ErrKV(ctx, "same message", "key", "addition value") + setStdout() + log.ErrKVs(ctx, "same message", "key", "addition value") // Output: msg="same message" key=addition value level=error } @@ -39,13 +45,13 @@ func ExampleNew_errf() { } func ExampleNew_debugKV() { - logger := log.New(log.WithStdout()).With(log.WithLevel(log.LevelDebug)) - logger.DebugKV(ctx, "same message", "error", os.ErrNotExist) + logger := log.New(log.WithStdout()).With(log.WithLevel("level", level.Debug)) + logger.DebugKVs(ctx, "same message", "error", os.ErrNotExist) // Output: msg="same message" error=file does not exist level=debug } func ExampleNew_level() { - logger := log.New(log.WithStdout()).With(log.WithLevel(log.LevelError)) + logger := log.New(log.WithStdout()).With(log.WithLevel("level", level.Error)) logger.Info(ctx, "same message") // Output: @@ -56,35 +62,25 @@ func ExampleNew_level() { func ExampleNew_jsonFormat() { logger := log.New(log.WithStdout(), log.WithJSONFormat()). With( - log.WithCaller(4, true), - log.WithLevel(log.LevelDebug), + log.WithLevel("level", level.Debug), log.GoVersion("go-version"), ) logger.Err(ctx, "same error message") - // Output: {"caller":"logger_example_test.go:63","go-version":"go1.14.2","level":"error","msg":"same error message"} + // Output: {"go-version":"go1.15.2","level":"error","msg":"same error message"} } -func ExampleNew_withLogger() { - stdlogger := std.New(os.Stdout, "same prefix ", std.Lshortfile) +func ExampleNew_textEncoding() { logger := log.With( - log.New( - log.WithLogger( - stdlogger, - func(msg string, fields log.Fields) string { - return fmt.Sprint("msg=\"", msg, "\" ", fields) - }, - ), - log.WithCalldepth(9), - ), - log.WithLevel(log.LevelDebug), + log.New(log.WithStdout()), + log.WithLevel("level", level.Debug), log.GoVersion("go-version"), ) logger.Err(ctx, "same error message") - logger.InfoKV(ctx, "same info message", "api-version", 0.1) + logger.InfoKVs(ctx, "same info message", "api-version", 0.1) // Output: - // same prefix logger_example_test.go:82: msg="same error message" level=error go-version=go1.14.2 - // same prefix logger_example_test.go:83: msg="same info message" api-version=0.1 level=info go-version=go1.14.2 + // msg="same error message" level=error go-version=go1.15.2 + // msg="same info message" api-version=0.1 level=info go-version=go1.15.2 } type ctxKey string @@ -93,8 +89,8 @@ func (c ctxKey) String() string { return string(c) } -func levelInfo(ctx context.Context, level log.Level, msg string, fields log.Fields, handler log.Logger) { - handler(ctx, level, msg, append(fields, log.Field{Key: "level", Value: level})) +func levelInfo(ctx context.Context, entry *entry.Entry, handler log.Logger) (int, error) { + return handler(ctx, entry.Add(field.String("level", entry.Level().String()))) } func ExampleWith() { @@ -106,7 +102,7 @@ func ExampleWith() { levelInfo, log.WithContextValue(requestID), log.KeyValue("api", "0.1.0"), log.GoVersion("go"), ) logger.Info(vctx, "same message") - // Output: msg="same message" level=info requestID=6a5fa048-7181-11ea-bc55-0242ac130003 api=0.1.0 go=go1.14.2 + // Output: msg="same message" level=info requestID=6a5fa048-7181-11ea-bc55-0242ac130003 api=0.1.0 go=go1.15.2 } func ExampleLogger_Print() { @@ -115,12 +111,31 @@ func ExampleLogger_Print() { levelInfo, log.KeyValue("client", "http"), log.KeyValue("api", "0.1.0"), log.GoVersion("go"), ) logger.Print("same message") - // Output: msg="same message" level=info client=http api=0.1.0 go=go1.14.2 + // Output: msg="same message" level=info client=http api=0.1.0 go=go1.15.2 } func ExamplePrint() { - std.SetOutput(os.Stdout) - std.SetFlags(0) + setStdout() log.Print("same message") // Output: msg="same message" level=info } + +func ExampleWithClosure() { + cnt := int32(0) + closure := func() string { + 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.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 +} diff --git a/logger_example_trace_test.go b/logger_example_trace_test.go new file mode 100644 index 0000000..cafb44d --- /dev/null +++ b/logger_example_trace_test.go @@ -0,0 +1,62 @@ +package log_test + +import ( + "context" + "fmt" + "io" + + "gitoa.ru/go-4devs/log" + "gitoa.ru/go-4devs/log/field" + "gitoa.ru/go-4devs/log/handler/otel" + apitrace "go.opentelemetry.io/otel/api/trace" + "go.opentelemetry.io/otel/sdk/export/trace" + sdktrace "go.opentelemetry.io/otel/sdk/trace" +) + +func ExampleNew_withTrace() { + logger := log.New(log.WithStdout()).With(otel.Middleware()) + + sctx, span := startSpan(ctx) + + logger.Err(sctx, "log logrus") + logger.ErrKV(sctx, "log logrus kv", field.Int("int", 42)) + logger.ErrKVs(sctx, "log logrus kv sugar", "err", io.EOF) + + span.End() + + // Output: + // msg="log logrus" + // msg="log logrus kv" int=42 + // msg="log logrus kv sugar" err=EOF + // event: log logrus, SeverityText = ERROR, SeverityNumber = 17 + // event: log logrus kv, SeverityText = ERROR, SeverityNumber = 17, int = 42 + // event: log logrus kv sugar, SeverityText = ERROR, SeverityNumber = 17, err = EOF +} + +func startSpan(ctx context.Context) (context.Context, apitrace.Span) { + tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter{})) + + return tp.Tracer("logger").Start(ctx, "operation") +} + +type exporter struct{} + +func (e exporter) Shutdown(_ context.Context) error { + return nil +} + +func (e exporter) ExportSpans(ctx context.Context, spanData []*trace.SpanData) error { + for _, data := range spanData { + for _, events := range data.MessageEvents { + fmt.Print("event: ", events.Name) + + for _, attr := range events.Attributes { + fmt.Printf(", %v = %v", attr.Key, attr.Value.AsInterface()) + } + + fmt.Print("\n") + } + } + + return nil +} diff --git a/logger_example_zap_test.go b/logger_example_zap_test.go new file mode 100644 index 0000000..d695668 --- /dev/null +++ b/logger_example_zap_test.go @@ -0,0 +1,28 @@ +package log_test + +import ( + "io" + "os" + + slogrus "github.com/sirupsen/logrus" + "gitoa.ru/go-4devs/log/field" + "gitoa.ru/go-4devs/log/handler/logrus" +) + +func ExampleNew_logrusHandler() { + lgrs := slogrus.New() + lgrs.SetOutput(os.Stdout) + lgrs.SetFormatter(&slogrus.TextFormatter{ + DisableTimestamp: true, + }) + + log := logrus.New(logrus.WithLogrus(lgrs)) + log.Err(ctx, "log logrus") + log.ErrKV(ctx, "log logrus kv", field.Int("int", 42)) + log.ErrKVs(ctx, "log logrus kv sugar", "err", io.EOF) + + // Output: + // level=error msg="log logrus" + // level=error msg="log logrus kv" int=42 + // level=error msg="log logrus kv sugar" err=EOF +} diff --git a/logger_test.go b/logger_test.go index af48b2e..b638402 100644 --- a/logger_test.go +++ b/logger_test.go @@ -4,28 +4,107 @@ import ( "bytes" "context" "os" + "sync/atomic" "testing" "gitoa.ru/go-4devs/log" + "gitoa.ru/go-4devs/log/entry" + "gitoa.ru/go-4devs/log/field" + "gitoa.ru/go-4devs/log/level" ) +//nolint:gochecknoglobals +var requestID ctxKey = "requestID" + func TestFields(t *testing.T) { type rObj struct { id string } + var cnt int32 + ctx := context.Background() buf := &bytes.Buffer{} - log := log.New(log.WithWriter(buf)) - success := "msg=\"message\" err=file already exists version=0.1.0 obj={id:uid}\n" + log := log.New(log.WithWriter(buf)). + With(log.WithLevel("level", level.Info), log.WithClosure) + success := "msg=\"message\" err=file already exists version=0.1.0 obj={id:uid} closure=some closure data level=info\n" - log.InfoKV(ctx, "message", + log.InfoKVs(ctx, "message", "err", os.ErrExist, "version", "0.1.0", "obj", rObj{id: "uid"}, + "closure", func() string { + atomic.AddInt32(&cnt, 1) + + return "some closure data" + }, ) + log.DebugKVs(ctx, "debug message", + "closure", func() string { + atomic.AddInt32(&cnt, 1) + + return "some debug data" + }, + ) + + if success != buf.String() { + t.Errorf("invalid value\n got:%s\n exp:%s", buf, success) + } + + if cnt != 1 { + t.Errorf("invalid cnt value\n got:%d\n exp:1", cnt) + } +} + +func TestWriter(t *testing.T) { + ctx := context.Background() + + success := "msg=\"info message\" err=file already exists requestID=6a5fa048-7181-11ea-bc55-0242ac1311113 level=info\n" + buf := &bytes.Buffer{} + logger := log.New(log.WithWriter(buf)).With(log.WithContextValue(requestID), log.WithLevel("level", level.Info)) + + _, _ = logger.Writer( + context.WithValue(ctx, requestID, "6a5fa048-7181-11ea-bc55-0242ac1311113"), + level.Info, + field.Error("err", os.ErrExist), + ).Write([]byte("info message")) + if success != buf.String() { t.Errorf("invalid value\n got:%s\n exp:%s", buf, success) } + + buf.Reset() + + _, _ = logger.Writer(ctx, level.Debug).Write([]byte("debug message")) + + if buf.String() != "" { + t.Errorf("invalid value\n got:%s\n exp:%s", buf, success) + } +} + +func TestLogger(t *testing.T) { + ctx := context.Background() + buf := &bytes.Buffer{} + logger := log.New(log.WithWriter(buf)).With(log.WithContextValue(requestID), log.WithLevel("level", level.Info)) + + _, err := logger(ctx, nil) + if err != nil { + t.Fatalf("expected err, got: %v", err) + } + + if buf.String() != "" { + t.Errorf("invalid value\n got:%+v\n exp:\"\"", buf) + } + + _, err = logger(ctx, entry.New().SetLevel(level.Error)) + if err != nil { + t.Fatalf("expected err, got: %v", err) + } + + success := "msg=\"\" requestID= level=error\n" + + if buf.String() != success { + t.Errorf("invalid value\n got:%+v\n exp:%+v", buf, success) + } } diff --git a/middleware.go b/middleware.go index 5103502..fa3e558 100644 --- a/middleware.go +++ b/middleware.go @@ -3,13 +3,19 @@ package log import ( "context" "fmt" - "path/filepath" + "os" "runtime" "time" + + "gitoa.ru/go-4devs/log/entry" + "gitoa.ru/go-4devs/log/field" + "gitoa.ru/go-4devs/log/level" ) +var _ Middleware = WithClosure + // Middleware handle. -type Middleware func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) +type Middleware func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) // With add middleware to logger. func With(logger Logger, mw ...Middleware) Logger { @@ -17,95 +23,129 @@ func With(logger Logger, mw ...Middleware) Logger { case 0: return logger case 1: - return func(ctx context.Context, level Level, msg string, fields Fields) { - mw[0](ctx, level, msg, fields, logger) + return func(ctx context.Context, entry *entry.Entry) (int, error) { + return mw[0](ctx, entry, logger) } } lastI := len(mw) - 1 - return func(ctx context.Context, level Level, msg string, fields Fields) { + return func(ctx context.Context, e *entry.Entry) (int, error) { var ( - chainHandler func(ctx context.Context, level Level, msg string, fields Fields) + chainHandler func(context.Context, *entry.Entry) (int, error) curI int ) - chainHandler = func(currentCtx context.Context, currentLevel Level, currentMsg string, currentFields Fields) { + chainHandler = func(currentCtx context.Context, currentEntry *entry.Entry) (int, error) { if curI == lastI { - logger(currentCtx, currentLevel, currentMsg, currentFields) - return + return logger(currentCtx, currentEntry) } curI++ - mw[curI](currentCtx, currentLevel, currentMsg, currentFields, chainHandler) + n, err := mw[curI](currentCtx, currentEntry, chainHandler) curI-- + + return n, err } - mw[0](ctx, level, msg, fields, chainHandler) + return mw[0](ctx, e, chainHandler) } } // WithLevel sets log level. -func WithLevel(lvl Level) Middleware { - return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) { - if level <= lvl { - handler(ctx, level, msg, append(fields, Field{Key: "level", Value: level})) +func WithLevel(key string, lvl level.Level) Middleware { + return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) { + if e.Level().Enabled(lvl) { + return handler(ctx, e.AddString(key, e.Level().String())) } + + return 0, nil } } +func WithClosure(ctx context.Context, e *entry.Entry, handler Logger) (int, error) { + for i, field := range e.Fields() { + if field.Type().IsAny() { + if f, ok := field.AsInterface().(func() string); ok { + e.Fields().Set(i, field.Key().String(f())) + } + } + } + + return handler(ctx, e) +} + // KeyValue add field by const key value. func KeyValue(key string, value interface{}) Middleware { - return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) { - handler(ctx, level, msg, append(fields, Field{Key: key, Value: value})) + return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) { + return handler(ctx, e.AddAny(key, value)) } } // GoVersion add field by go version. func GoVersion(key string) Middleware { - return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) { - handler(ctx, level, msg, append(fields, Field{Key: key, Value: runtime.Version()})) + return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) { + return handler(ctx, e.AddString(key, runtime.Version())) } } // WithContext add field by context key. func WithContextValue(keys ...fmt.Stringer) Middleware { - return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) { - ctxFields := make(Fields, len(keys)) - for i, key := range keys { - ctxFields[i] = Field{Key: key.String(), Value: ctx.Value(key)} + return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) { + for _, key := range keys { + e = e.AddAny(key.String(), ctx.Value(key)) } - handler(ctx, level, msg, append(fields, ctxFields...)) + return handler(ctx, e) } } // WithCaller adds called file. -func WithCaller(calldepth int, short bool) Middleware { - return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) { - _, file, line, ok := runtime.Caller(calldepth) - if !ok { - file, line = "???", 0 - } - - if short && ok { - file = filepath.Base(file) - } +func WithCaller(key string, depth int, full bool) Middleware { + const offset = 2 - handler(ctx, level, msg, append(fields, NewField("caller", fmt.Sprint(file, ":", line)))) + return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) { + return handler(ctx, e.AddString(key, entry.Caller(depth*offset, full))) } } // WithTime adds time. -func WithTime(format string) Middleware { - return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) { - handler(ctx, level, msg, append(fields, NewField("time", time.Now().Format(format)))) +func WithTime(key, format string) Middleware { + return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) { + return handler(ctx, e.Add(field.Time(key, time.Now()))) } } // WithMetrics adds handle metrics. -func WithMetrics(metrics func(level Level)) Middleware { - return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) { - go metrics(level) - handler(ctx, level, msg, fields) +func WithMetrics(metrics func(level level.Level)) Middleware { + return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) { + go metrics(e.Level()) + + return handler(ctx, e) + } +} + +// WithExit exit by level. +func WithExit(level level.Level) Middleware { + return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) { + n, err := handler(ctx, e) + + if e.Level().Is(level) { + os.Exit(1) + } + + return n, err + } +} + +// WithPanic panic by level. +func WithPanic(level level.Level) Middleware { + return func(ctx context.Context, e *entry.Entry, handler Logger) (int, error) { + n, err := handler(ctx, e) + + if e.Level().Is(level) { + panic(e.String()) + } + + return n, err } } diff --git a/std.go b/std.go deleted file mode 100644 index dc102c9..0000000 --- a/std.go +++ /dev/null @@ -1,121 +0,0 @@ -package log - -import ( - "context" - "encoding/json" - "fmt" - "io" - "log" - "os" -) - -const ( - calldepth = 3 -) - -// New creates standart logger. -func New(opts ...Option) Logger { - logger := logger{ - format: stringFormat, - output: log.Output, - calldepth: calldepth, - } - - for _, opt := range opts { - opt(&logger) - } - - return func(ctx context.Context, level Level, msg string, fields Fields) { - _ = logger.output(logger.calldepth, logger.format(msg, fields)) - - switch level { - case LevelEmergency: - panic(msg) - case LevelAlert: - os.Exit(1) - default: - } - } -} - -// Option configure log. -type Option func(*logger) - -// Format sets formats output message. -type Format func(msg string, fields Fields) string - -type logger struct { - output func(calldepth int, s string) error - format Format - calldepth int -} - -// WithWriter sets writer logger. -func WithWriter(writer io.Writer) Option { - return func(l *logger) { - l.output = log.New(writer, "", 0).Output - } -} - -// WithStdout sets logged to os.Stdout. -func WithStdout() Option { - return func(l *logger) { - l.output = log.New(os.Stdout, "", 0).Output - } -} - -// WithFormat sets format log. -func WithFormat(format Format) Option { - return func(l *logger) { - l.format = format - } -} - -// WithStringFormat sets format as simple string. -func WithStringFormat() Option { - return func(l *logger) { - l.format = stringFormat - } -} - -// WithJSONFormat sets json output format. -func WithJSONFormat() Option { - return func(l *logger) { - l.format = jsonFormat - } -} - -// WithCalldepth sets depth filename. -func WithCalldepth(calldepth int) Option { - return func(l *logger) { - l.calldepth = calldepth - } -} - -// WithLogger sets logger anf format. -func WithLogger(std *log.Logger, format Format) Option { - return func(l *logger) { - l.output = std.Output - l.format = format - } -} - -func stringFormat(msg string, fields Fields) string { - return fmt.Sprint("msg=\"", msg, "\" ", fields) -} - -func jsonFormat(msg string, fields Fields) string { - data := make(map[string]interface{}, len(fields)+1) - data["msg"] = msg - - for _, field := range fields { - data[field.Key] = field.Value - } - - res, err := json.Marshal(data) - if err != nil { - return stringFormat(msg, append(fields, FieldError(err))) - } - - return string(res) -} diff --git a/writter.go b/writter.go new file mode 100644 index 0000000..3a7d13b --- /dev/null +++ b/writter.go @@ -0,0 +1,118 @@ +package log + +import ( + "bytes" + "context" + "encoding/json" + "io" + "os" + "strings" + "sync" + + "gitoa.ru/go-4devs/log/entry" +) + +// New creates standart logger. +func New(opts ...Option) Logger { + l := log{e: stringFormat(), w: os.Stderr} + + for _, opt := range opts { + opt(&l) + } + + return func(_ context.Context, entry *entry.Entry) (int, error) { + b, err := l.e(entry) + if err != nil { + return 0, err + } + + return l.w.Write(b) + } +} + +// 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 +} + +// WithWriter sets writer logger. +func WithWriter(writer io.Writer) Option { + return func(l *log) { + l.w = writer + } +} + +// WithStdout sets logged to os.Stdout. +func WithStdout() Option { + return func(l *log) { + l.w = os.Stdout + } +} + +// WithEncode sets format log. +func WithEncode(e Encode) Option { + return func(l *log) { + l.e = e + } +} + +// WithStringFormat sets format as simple string. +func WithStringFormat() Option { + return func(l *log) { + l.e = stringFormat() + } +} + +// WithJSONFormat sets json output format. +func WithJSONFormat() Option { + return func(l *log) { + l.e = jsonFormat + } +} + +func stringFormat() func(entry *entry.Entry) ([]byte, error) { + pool := sync.Pool{ + New: func() interface{} { + return &bytes.Buffer{} + }, + } + + return func(entry *entry.Entry) ([]byte, error) { + b := pool.Get().(*bytes.Buffer) + b.Reset() + + defer func() { + pool.Put(b) + }() + + b.WriteString("msg=\"") + b.WriteString(strings.TrimSpace(entry.Message())) + b.WriteString("\"") + + for _, field := range entry.Fields() { + b.WriteString(" ") + b.WriteString(string(field.Key())) + b.WriteString("=") + b.WriteString(field.Value().String()) + } + + b.WriteString("\n") + + return b.Bytes(), nil + } +} + +func jsonFormat(entry *entry.Entry) ([]byte, error) { + res, err := json.Marshal(entry.AddString("msg", entry.Message()).Fields().AsMap()) + if err != nil { + return nil, err + } + + return append(res, []byte("\n")...), nil +}