Repository: go-logr/logr (go)

Issue:
[funcr.NewJSON] Naive RenderValuesHook incorrectly triggered recursive labels
This isn't an issue with `go-logr` but it may be worth identifying as a potential trap for the unwary; I *will* update my blog [post](https://pretired.dazwilkin.com/posts/211018/).

My naive implementation of `RenderValuesHook` was:

```golang
renderLabels := func(kvList []interface{}) []interface{} {
  // loggingLabels becomes the only label key
  return []interface{}{
    loggingLabels,
    funcr.PseudoStruct(kvList),
  }
}
```

This worked fine if `WithValues` is only called once per Logger. If `WithValues` is called again, the existing labels are nested within the new set, i.e.:

```JSON
{
  "jsonPayload": {
    "logging.googleapis.com/labels": {
      "logging.googleapis.com/labels": { <--- INCORRECT
        "foo": "X"
      },
      "bar": "Y"
    }
  }
  "labels": {
    "instanceId": "00bf4bf0..."
  }
}
``` 

> **NOTE** In my case, Google Cloud Logging no longer recognized the borked `logging.googleapis.com/labels` as special values and does not merge them with the `labels` field.

Using [`NewStdoutLogger`](https://github.com/go-logr/logr/blob/8d00e357f1cbbb9246ed8bbf03f2c8fe242d2ae9/example_test.go#L27), the issue does not occur:

```Golang
	l := NewStdoutLogger()
	l.Info("info")

	l = l.WithValues("foo", "X")
	l.Info("info")

	l = l.WithValues("bar", "Y")
	l.Info("info")
```
Outputs:

```console
"level"=0 "msg"="info"
"level"=0 "msg"="info" "foo"="X"
"level"=0 "msg"="info" "foo"="X" "bar"="Y"
```

So, it's an issue with my implementation.

I revised the implementation (improvements welcome):

```golang
renderLabels := func(kvList []interface{}) []interface{} {
  // If loggingLabels is present, it will be the first label key
  k, _ := kvList[0].(string)
  if k == loggingLabels && len(kvList) > 2 {
    // The label values should be a PseudoStruct
    v, _ := kvList[1].(funcr.PseudoStruct)
    // Append additional ([2:]) label keys|values to the existing loggingLabels
    // loggingLabels becomes the only label key
      return []interface{}{
        loggingLabels,
        append(v, funcr.PseudoStruct(kvList[2:])...),
      }
    }

    // loggingLabels becomes the only label key
    return []interface{}{
      loggingLabels,
      funcr.PseudoStruct(kvList),
    }
}
```

And, replacing `NewStdoutLogger` with `NewStructuredLogger`, I get:

```console
{"level":0,"msg":"info"}
{"level":0,"msg":"info","logging.googleapis.com/labels":{"foo":"X"}}
{"level":0,"msg":"info","logging.googleapis.com/labels":{"foo":"X","bar":"Y"}}
```




Pull request:
funcr: Fix bug in repeated calls to WithValues
When using `RenderValuesHook` we need to save the raw value, not the
rendered form.

Fixes #120

@pohly let's not sit on it too long - we can tag a 1.2.1 maybe next week?

@DazWilkin FYI

Patch:
diff --git a/funcr/funcr.go b/funcr/funcr.go
index 8ecf434..4727d27 100644
--- a/funcr/funcr.go
+++ b/funcr/funcr.go
@@ -722,15 +722,16 @@ func (f *Formatter) AddName(name string) {
 func (f *Formatter) AddValues(kvList []interface{}) {
 	// Three slice args forces a copy.
 	n := len(f.values)
-	vals := f.values[:n:n]
-	vals = append(vals, kvList...)
+	f.values = append(f.values[:n:n], kvList...)
+
+	vals := f.values
 	if hook := f.opts.RenderValuesHook; hook != nil {
 		vals = hook(f.sanitize(vals))
 	}
 
 	// Pre-render values, so we don't have to do it on each Info/Error call.
 	buf := bytes.NewBuffer(make([]byte, 0, 1024))
-	f.values = f.flatten(buf, vals, false, true) // escape user-provided keys
+	f.flatten(buf, vals, false, true) // escape user-provided keys
 	f.valuesStr = buf.String()
 }
 


Test patch:
diff --git a/funcr/example_test.go b/funcr/example_test.go
index 1e1a2b2..9197a9a 100644
--- a/funcr/example_test.go
+++ b/funcr/example_test.go
@@ -94,9 +94,10 @@ func ExampleOptions_renderHooks() {
 			RenderValuesHook:   valuesAsObject,
 		})
 	log = log.WithName("MyLogger")
-	log = log.WithValues("savedKey", "savedValue")
+	log = log.WithValues("savedKey1", "savedVal1")
+	log = log.WithValues("savedKey2", "savedVal2")
 	log.Info("the message", "key", "value")
-	// Output: {"log:logger":"MyLogger","log:level":0,"log:msg":"the message","labels":{"savedKey":"savedValue"},"key":"value"}
+	// Output: {"log:logger":"MyLogger","log:level":0,"log:msg":"the message","labels":{"savedKey1":"savedVal1","savedKey2":"savedVal2"},"key":"value"}
 }
 
 func ExamplePseudoStruct() {