mlog: make the map returned from a KVer be read-only and allowed to be nil

This commit is contained in:
Brian Picciano 2019-01-14 22:47:16 -05:00
parent 1562c4e8f6
commit 33a10a4ac7
2 changed files with 25 additions and 30 deletions

View File

@ -76,7 +76,7 @@ var (
// KVer is used to provide context to a log entry in the form of a dynamic set
// of key/value pairs which can be different for every entry.
//
// Each returned map should be modifiable.
// The returned map is read-only, and may be nil.
type KVer interface {
KV() map[string]interface{}
}
@ -94,19 +94,16 @@ type KV map[string]interface{}
// KV implements the KVer method by returning a copy of itself.
func (kv KV) KV() map[string]interface{} {
nkv := make(map[string]interface{}, len(kv))
for k, v := range kv {
nkv[k] = v
}
return nkv
return map[string]interface{}(kv)
}
// Set returns a copy of the KV being called on with the given key/val set on
// it. The original KV is unaffected
func (kv KV) Set(k string, v interface{}) KV {
nkv := kv.KV()
nkv[k] = v
return nkv
kvm := make(map[string]interface{}, len(kv)+1)
copyM(kvm, kv.KV())
kvm[k] = v
return KV(kvm)
}
// returns a key/value map which should not be written to. saves a map-cloning
@ -120,16 +117,23 @@ func readOnlyKVM(kver KVer) map[string]interface{} {
return kver.KV()
}
func copyM(dst, src map[string]interface{}) {
for k, v := range src {
dst[k] = v
}
}
// this may take in any amount of nil values, but should never return nil
func mergeInto(kv KVer, kvs ...KVer) map[string]interface{} {
if kv == nil {
kv = KV(nil) // will return empty map when KV is called on it
kvm := map[string]interface{}{}
if kv != nil {
copyM(kvm, kv.KV())
}
kvm := kv.KV()
for _, innerKV := range kvs {
for k, v := range readOnlyKVM(innerKV) {
kvm[k] = v
if innerKV == nil {
continue
}
copyM(kvm, innerKV.KV())
}
return kvm
}
@ -321,9 +325,10 @@ func (l *Logger) WithKV(kvs ...KVer) *Logger {
return l
}
// Log can be used to manually log a message of some custom defined Level. kvs
// will be Merge'd automatically. If the Level is a fatal (Uint() == 0) then
// calling this will never return, and the process will have os.Exit(1) called.
// Log can be used to manually log a message of some custom defined Level.
//
// If the Level is a fatal (Uint() == 0) then calling this will never return,
// and the process will have os.Exit(1) called.
func (l *Logger) Log(msg Message) {
if l.maxLevel < msg.Level.Uint() {
return

View File

@ -21,27 +21,17 @@ func TestTruncate(t *T) {
func TestKV(t *T) {
var kv KV
massert.Fatal(t, massert.All(
massert.Not(massert.Nil(kv.KV())),
massert.Nil(kv.KV()),
massert.Len(kv.KV(), 0),
))
// test that the KV method returns a copy
kv = KV{"foo": "a"}
kvm := kv.KV()
kv["bur"] = "b"
kvm["bar"] = "bb"
massert.Fatal(t, massert.All(
massert.Equal(KV{"foo": "a", "bur": "b"}, kv),
massert.Equal(map[string]interface{}{"foo": "a", "bar": "bb"}, kvm),
))
// test that the Set method returns a copy
kv = KV{"foo": "a"}
kvm = kv.Set("bar", "wat")
kv2 := kv.Set("bar", "wat")
kv["bur"] = "ok"
massert.Fatal(t, massert.All(
massert.Equal(KV{"foo": "a", "bur": "ok"}, kv),
massert.Equal(map[string]interface{}{"foo": "a", "bar": "wat"}, kvm),
massert.Equal(KV{"foo": "a", "bar": "wat"}, kv2),
))
}