From a02d145a7f72b81c9e7372d60da75cbbc76fbf6d Mon Sep 17 00:00:00 2001 From: Ben Schumacher Date: Tue, 14 Nov 2023 09:16:25 +0100 Subject: [PATCH] Simplify Field API using generics (#44) Co-authored-by: Doug Lauder --- field.go | 40 ++++++------- fieldapi.go | 33 ++++++++--- fieldapi_test.go | 118 +++++++++++++++++++++++++++++++++++--- formatters/json_test.go | 2 +- test/cmd/metrics/main.go | 6 +- test/cmd/testapp1/main.go | 6 +- test/load.go | 4 +- 7 files changed, 163 insertions(+), 46 deletions(-) diff --git a/field.go b/field.go index 3334287..9773baf 100644 --- a/field.go +++ b/field.go @@ -269,19 +269,19 @@ func fieldForAny(key string, val interface{}) Field { } return Bool(key, *v) case float64: - return Float64(key, v) + return Float(key, v) case *float64: if v == nil { return nilField(key) } - return Float64(key, *v) + return Float(key, *v) case float32: - return Float32(key, v) + return Float(key, v) case *float32: if v == nil { return nilField(key) } - return Float32(key, *v) + return Float(key, *v) case int: return Int(key, v) case *int: @@ -290,33 +290,33 @@ func fieldForAny(key string, val interface{}) Field { } return Int(key, *v) case int64: - return Int64(key, v) + return Int(key, v) case *int64: if v == nil { return nilField(key) } - return Int64(key, *v) + return Int(key, *v) case int32: - return Int32(key, v) + return Int(key, v) case *int32: if v == nil { return nilField(key) } - return Int32(key, *v) + return Int(key, *v) case int16: - return Int32(key, int32(v)) + return Int(key, int32(v)) case *int16: if v == nil { return nilField(key) } - return Int32(key, int32(*v)) + return Int(key, int32(*v)) case int8: - return Int32(key, int32(v)) + return Int(key, int32(v)) case *int8: if v == nil { return nilField(key) } - return Int32(key, int32(*v)) + return Int(key, int32(*v)) case string: return String(key, v) case *string: @@ -332,33 +332,33 @@ func fieldForAny(key string, val interface{}) Field { } return Uint(key, *v) case uint64: - return Uint64(key, v) + return Uint(key, v) case *uint64: if v == nil { return nilField(key) } - return Uint64(key, *v) + return Uint(key, *v) case uint32: - return Uint32(key, v) + return Uint(key, v) case *uint32: if v == nil { return nilField(key) } - return Uint32(key, *v) + return Uint(key, *v) case uint16: - return Uint32(key, uint32(v)) + return Uint(key, uint32(v)) case *uint16: if v == nil { return nilField(key) } - return Uint32(key, uint32(*v)) + return Uint(key, uint32(*v)) case uint8: - return Uint32(key, uint32(v)) + return Uint(key, uint32(v)) case *uint8: if v == nil { return nilField(key) } - return Uint32(key, uint32(*v)) + return Uint(key, uint32(*v)) case []byte: if v == nil { return nilField(key) diff --git a/fieldapi.go b/fieldapi.go index d230035..25a6615 100644 --- a/fieldapi.go +++ b/fieldapi.go @@ -9,53 +9,70 @@ import ( // For best performance when passing a struct (or struct pointer), // implement `logr.LogWriter` on the struct, otherwise reflection // will be used to generate a string representation. -func Any(key string, val interface{}) Field { +func Any(key string, val any) Field { return fieldForAny(key, val) } // Int64 constructs a field containing a key and Int64 value. +// +// Deprecated: Use [logr.Int] instead. func Int64(key string, val int64) Field { return Field{Key: key, Type: Int64Type, Integer: val} } // Int32 constructs a field containing a key and Int32 value. +// +// Deprecated: Use [logr.Int] instead. func Int32(key string, val int32) Field { return Field{Key: key, Type: Int32Type, Integer: int64(val)} } -// Int constructs a field containing a key and Int value. -func Int(key string, val int) Field { +// Int constructs a field containing a key and int value. +func Int[T ~int | ~int8 | ~int16 | ~int32 | ~int64](key string, val T) Field { return Field{Key: key, Type: IntType, Integer: int64(val)} } // Uint64 constructs a field containing a key and Uint64 value. +// +// Deprecated: Use [logr.Uint] instead. func Uint64(key string, val uint64) Field { return Field{Key: key, Type: Uint64Type, Integer: int64(val)} } // Uint32 constructs a field containing a key and Uint32 value. +// +// Deprecated: Use [logr.Uint] instead func Uint32(key string, val uint32) Field { return Field{Key: key, Type: Uint32Type, Integer: int64(val)} } -// Uint constructs a field containing a key and Uint value. -func Uint(key string, val uint) Field { +// Uint constructs a field containing a key and uint value. +func Uint[T ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr](key string, val T) Field { return Field{Key: key, Type: UintType, Integer: int64(val)} } // Float64 constructs a field containing a key and Float64 value. +// +// Deprecated: Use [logr.Float] instead func Float64(key string, val float64) Field { return Field{Key: key, Type: Float64Type, Float: val} } // Float32 constructs a field containing a key and Float32 value. +// +// Deprecated: Use [logr.Float] instead func Float32(key string, val float32) Field { return Field{Key: key, Type: Float32Type, Float: float64(val)} } +// Float32 constructs a field containing a key and float value. +func Float[T ~float32 | ~float64](key string, val T) Field { + return Field{Key: key, Type: Float32Type, Float: float64(val)} +} + // String constructs a field containing a key and String value. -func String(key string, val string) Field { - return Field{Key: key, Type: StringType, String: val} +func String[T ~string](key string, val T) Field { + return Field{Key: key, Type: StringType, String: string(val)} } // Stringer constructs a field containing a key and a `fmt.Stringer` value. @@ -75,7 +92,7 @@ func NamedErr(key string, err error) Field { } // Bool constructs a field containing a key and bool value. -func Bool(key string, val bool) Field { +func Bool[T ~bool](key string, val T) Field { var b int64 if val { b = 1 diff --git a/fieldapi_test.go b/fieldapi_test.go index 177c860..465ba5d 100644 --- a/fieldapi_test.go +++ b/fieldapi_test.go @@ -1,25 +1,125 @@ package logr -import "testing" +import ( + "errors" + "testing" + "time" +) + +func TestFieldInt(t *testing.T) { + _ = Int("int", int(0)) + type myInt int + _ = Int("int", myInt(0)) + + _ = Int("int8", int8(0)) + type myInt8 int8 + _ = Int("int8", myInt8(0)) + + _ = Int("int16", int16(0)) + type myInt16 int16 + _ = Int("int16", myInt16(0)) + + _ = Int("int32", int32(0)) + type myInt32 int32 + _ = Int("int32", myInt32(0)) + + _ = Int("int64", int64(0)) + type myInt64 int64 + _ = Int("int64", myInt64(0)) +} + +func TestFieldUint(t *testing.T) { + _ = Uint("uint", uint(0)) + type myUint uint + _ = Uint("uint", myUint(0)) + + _ = Uint("uint8", uint8(0)) + type myUint8 uint8 + _ = Uint("uint8", myUint8(0)) + + _ = Uint("uint16", uint16(0)) + type myUint16 uint16 + _ = Uint("uint16", myUint16(0)) + + _ = Uint("uint32", uint32(0)) + type myUint32 uint32 + _ = Uint("uint32", myUint32(0)) + + _ = Uint("uint64", uint64(0)) + type myUint64 uint64 + _ = Uint("uint64", myUint64(0)) + + _ = Uint("uintptr", uintptr(0)) + type myUintptr uintptr + _ = Uint("uintptr", myUintptr(0)) +} + +func TestFieldFloat(t *testing.T) { + _ = Float("float32", float32(0)) + type myFloat32 float32 + _ = Float("float32", myFloat32(0)) + + _ = Float("float64", float64(0)) + type myFloat64 float32 + _ = Float("float64", myFloat64(0)) +} + +func TestFieldString(t *testing.T) { + _ = String("string", "foo") + type myString string + _ = String("string", myString("foo")) +} + +func TestFieldStringer(t *testing.T) { + _ = Stringer("stringer", time.Now()) + type myStringer = time.Time + _ = Stringer("stringer", myStringer(time.Now())) +} + +func TestFieldErr(t *testing.T) { + _ = Err(errors.New("some error")) + type myError error + _ = Err(myError(errors.New("some error"))) +} + +func TestFieldNamedErr(t *testing.T) { + _ = NamedErr("named err", errors.New("some error")) + type myError error + _ = NamedErr("named err", myError(errors.New("some error"))) +} + +func TestFieldBool(t *testing.T) { + _ = Bool("bool", false) + type myBool bool + _ = Bool("bool", myBool(false)) +} + +func TestFieldDuration(t *testing.T) { + _ = Duration("duration", time.Duration(0)) +} + +func TestFieldMillis(t *testing.T) { + _ = Millis("millis", int64(0)) +} func TestFieldArray(t *testing.T) { - Array("array", []string{}) - Array("array", []any{}) + _ = Array("array", []string{}) + _ = Array("array", []any{}) type myArray []any - Array("array", myArray{}) + _ = Array("array", myArray{}) type myGenericArray[T any] []T - Array("array", myGenericArray[any]{}) + _ = Array("array", myGenericArray[any]{}) } func TestFieldMap(t *testing.T) { - Map("array", map[string]any{}) - Map("array", map[int]any{}) + _ = Map("array", map[string]any{}) + _ = Map("array", map[int]any{}) type myMap map[string]string - Map("array", myMap{}) + _ = Map("array", myMap{}) type myGenericMap[K comparable, V any] map[K]V - Map("array", myGenericMap[int, any]{}) + _ = Map("array", myGenericMap[int, any]{}) } diff --git a/formatters/json_test.go b/formatters/json_test.go index 3d8f9b8..b78d5e9 100644 --- a/formatters/json_test.go +++ b/formatters/json_test.go @@ -49,7 +49,7 @@ func TestJSONFieldTypes(t *testing.T) { logr.String("f1", "one"), logr.Int("f2", 77), logr.Bool("f3", true), - logr.Float64("f4", 3.14), + logr.Float("f4", 3.14), logr.Err(errors.New("test error")), ) err = lgr.Flush() diff --git a/test/cmd/metrics/main.go b/test/cmd/metrics/main.go index 4202811..96bf970 100644 --- a/test/cmd/metrics/main.go +++ b/test/cmd/metrics/main.go @@ -116,9 +116,9 @@ func main() { logged2, filtered2 := test.DoSomeLogging(cfg) lgr.NewLogger().Log(lvl, "Logr test completed.", - logr.Uint32("errors", atomic.LoadUint32(&errorCount)), - logr.Uint32("queueFull", atomic.LoadUint32(&queueFullCount)), - logr.Uint32("targetFull", atomic.LoadUint32(&targetQueueFullCount)), + logr.Uint("errors", atomic.LoadUint32(&errorCount)), + logr.Uint("queueFull", atomic.LoadUint32(&queueFullCount)), + logr.Uint("targetFull", atomic.LoadUint32(&targetQueueFullCount)), ) close(done) diff --git a/test/cmd/testapp1/main.go b/test/cmd/testapp1/main.go index 291b1e5..7a76af4 100644 --- a/test/cmd/testapp1/main.go +++ b/test/cmd/testapp1/main.go @@ -115,9 +115,9 @@ func main() { logged2, filtered2 := test.DoSomeLogging(cfg) lgr.NewLogger().Log(lvl, "Logr test completed.", - logr.Uint32("errors", atomic.LoadUint32(&errorCount)), - logr.Uint32("queueFull", atomic.LoadUint32(&queueFullCount)), - logr.Uint32("targetFull", atomic.LoadUint32(&targetQueueFullCount)), + logr.Uint("errors", atomic.LoadUint32(&errorCount)), + logr.Uint("queueFull", atomic.LoadUint32(&queueFullCount)), + logr.Uint("targetFull", atomic.LoadUint32(&targetQueueFullCount)), ) close(done) diff --git a/test/load.go b/test/load.go index eac114d..70b6f96 100644 --- a/test/load.go +++ b/test/load.go @@ -41,7 +41,7 @@ func DoSomeLogging(cfg DoSomeLoggingCfg) (logged int32, filtered int32) { defer wg.Done() tid := atomic.AddInt32(&id, 1) logger := cfg.Lgr.NewLogger().With( - logr.Int32("id", tid), + logr.Int("id", tid), logr.Int("rnd", rand.Intn(100)), ) @@ -52,7 +52,7 @@ func DoSomeLogging(cfg DoSomeLoggingCfg) (logged int32, filtered int32) { } lc := atomic.AddInt32(&logCount, 1) logger.Log(cfg.Lvl, "This is some sample text.", - logr.Int32("count", lc), + logr.Int("count", lc), logr.String("good", cfg.GoodToken), )