From b038b726c468ceac2162c5c9d34ee08463de51fd Mon Sep 17 00:00:00 2001 From: Nail Islamov Date: Wed, 5 Aug 2020 15:35:06 +1000 Subject: [PATCH 1/6] Fix log adapter in Prometheus receiver --- .../prometheusreceiver/internal/logger.go | 82 ++++++++++++- .../internal/logger_test.go | 108 ++++++++++++++++++ 2 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 receiver/prometheusreceiver/internal/logger_test.go diff --git a/receiver/prometheusreceiver/internal/logger.go b/receiver/prometheusreceiver/internal/logger.go index fd0a4404506..865b7b66d40 100644 --- a/receiver/prometheusreceiver/internal/logger.go +++ b/receiver/prometheusreceiver/internal/logger.go @@ -15,8 +15,12 @@ package internal import ( + "fmt" + gokitLog "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log/level" "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) // NewZapToGokitLogAdapter create an adapter for zap.Logger to gokitLog.Logger @@ -32,9 +36,14 @@ type zapToGokitLogAdapter struct { } func (w *zapToGokitLogAdapter) Log(keyvals ...interface{}) error { + // expecting key value pairs, the number of items need to be even if len(keyvals)%2 == 0 { - // expecting key value pairs, the number of items need to be even - w.l.Infow("", keyvals...) + zapLevel, keyvals := extractLogLevel(keyvals) + logFunc, err := levelToFunc(w.l, zapLevel) + if err != nil { + return err + } + logFunc("", keyvals...) } else { // in case something goes wrong w.l.Info(keyvals...) @@ -42,4 +51,73 @@ func (w *zapToGokitLogAdapter) Log(keyvals ...interface{}) error { return nil } +func extractLogLevel(keyvals []interface{}) (zapcore.Level, []interface{}) { + zapLevel := zapcore.InfoLevel + output := make([]interface{}, 0, len(keyvals)) + for i := 0; i < len(keyvals); i = i + 2 { + key := keyvals[i] + val := keyvals[i+1] + + if l, ok := matchLogLevel(key, val); ok { + zapLevel = *l + continue + } + + output = append(output, key, val) + } + + return zapLevel, output +} + +func matchLogLevel(key interface{}, val interface{}) (*zapcore.Level, bool) { + strKey, ok := key.(string) + if !ok || strKey != "level" { + return nil, false + } + + levelVal, ok := val.(level.Value) + if !ok { + return nil, false + } + + zapLevel := toZapLevel(levelVal) + return &zapLevel, true +} + +func toZapLevel(value level.Value) zapcore.Level { + // See https://github.com/go-kit/kit/blob/556100560949062d23fe05d9fda4ce173c30c59f/log/level/level.go#L184-L187 + switch value.String() { + case "error": + return zapcore.ErrorLevel + case "warn": + return zapcore.WarnLevel + case "info": + return zapcore.InfoLevel + case "debug": + return zapcore.DebugLevel + default: + return zapcore.InfoLevel + } +} + +func levelToFunc(logger *zap.SugaredLogger, lvl zapcore.Level) (func(string, ...interface{}), error) { + switch lvl { + case zapcore.DebugLevel: + return logger.Debugf, nil + case zapcore.InfoLevel: + return logger.Infof, nil + case zapcore.WarnLevel: + return logger.Warnf, nil + case zapcore.ErrorLevel: + return logger.Errorf, nil + case zapcore.DPanicLevel: + return logger.DPanicf, nil + case zapcore.PanicLevel: + return logger.Panicf, nil + case zapcore.FatalLevel: + return logger.Fatalf, nil + } + return nil, fmt.Errorf("unrecognized level: %q", lvl) +} + var _ gokitLog.Logger = (*zapToGokitLogAdapter)(nil) diff --git a/receiver/prometheusreceiver/internal/logger_test.go b/receiver/prometheusreceiver/internal/logger_test.go new file mode 100644 index 00000000000..ac7fc8c3c29 --- /dev/null +++ b/receiver/prometheusreceiver/internal/logger_test.go @@ -0,0 +1,108 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "testing" + + "github.com/bmizerany/assert" + "github.com/go-kit/kit/log/level" + "go.uber.org/zap/zapcore" +) + +func TestExtractLogLevel(t *testing.T) { + tcs := []struct { + name string + input []interface{} + wantLevel zapcore.Level + wantOutput []interface{} + }{ + { + name: "nil fields", + input: nil, + wantLevel: zapcore.InfoLevel, // Default + wantOutput: []interface{}{}, + }, + { + name: "empty fields", + input: []interface{}{}, + wantLevel: zapcore.InfoLevel, // Default + wantOutput: []interface{}{}, + }, + { + name: "warn level", + input: []interface{}{ + "level", + level.WarnValue(), + }, + wantLevel: zapcore.WarnLevel, + wantOutput: []interface{}{}, + }, + { + name: "debug level + extra fields", + input: []interface{}{ + "ts", + 1596604719.955769, + "level", + level.DebugValue(), + "msg", + "http client error", + }, + wantLevel: zapcore.DebugLevel, + wantOutput: []interface{}{ + "ts", + 1596604719.955769, + "msg", + "http client error", + }, + }, + { + name: "missing level field", + input: []interface{}{ + "ts", + 1596604719.955769, + "msg", + "http client error", + }, + wantLevel: zapcore.InfoLevel, // Default + wantOutput: []interface{}{ + "ts", + 1596604719.955769, + "msg", + "http client error", + }, + }, + { + name: "invalid level type", + input: []interface{}{ + "level", + "warn", // String is not recognized + }, + wantLevel: zapcore.InfoLevel, // Default + wantOutput: []interface{}{ + "level", + "warn", // Field is preserved + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + zapLevel, output := extractLogLevel(tc.input) + assert.Equal(t, tc.wantLevel, zapLevel) + assert.Equal(t, tc.wantOutput, output) + }) + } +} From 5e3e2dae677c14f74b665ea9b557a6367308f804 Mon Sep 17 00:00:00 2001 From: Nail Islamov Date: Wed, 5 Aug 2020 15:41:11 +1000 Subject: [PATCH 2/6] Add comments to new functions --- receiver/prometheusreceiver/internal/logger.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/receiver/prometheusreceiver/internal/logger.go b/receiver/prometheusreceiver/internal/logger.go index 865b7b66d40..d7eb5f4f7c8 100644 --- a/receiver/prometheusreceiver/internal/logger.go +++ b/receiver/prometheusreceiver/internal/logger.go @@ -51,6 +51,8 @@ func (w *zapToGokitLogAdapter) Log(keyvals ...interface{}) error { return nil } +// extract go-kit log level from key value pairs, convert it to zap log level +// and remove from the list to avoid duplication in log message func extractLogLevel(keyvals []interface{}) (zapcore.Level, []interface{}) { zapLevel := zapcore.InfoLevel output := make([]interface{}, 0, len(keyvals)) @@ -69,6 +71,8 @@ func extractLogLevel(keyvals []interface{}) (zapcore.Level, []interface{}) { return zapLevel, output } +// check if a given key-value pair represents go-kit log level and return +// a corresponding zap log level func matchLogLevel(key interface{}, val interface{}) (*zapcore.Level, bool) { strKey, ok := key.(string) if !ok || strKey != "level" { @@ -84,6 +88,7 @@ func matchLogLevel(key interface{}, val interface{}) (*zapcore.Level, bool) { return &zapLevel, true } +// convert go-kit log level to zap func toZapLevel(value level.Value) zapcore.Level { // See https://github.com/go-kit/kit/blob/556100560949062d23fe05d9fda4ce173c30c59f/log/level/level.go#L184-L187 switch value.String() { @@ -100,6 +105,7 @@ func toZapLevel(value level.Value) zapcore.Level { } } +// find a matching zap logging function to be used for a given level func levelToFunc(logger *zap.SugaredLogger, lvl zapcore.Level) (func(string, ...interface{}), error) { switch lvl { case zapcore.DebugLevel: From c5832f9c7c1a05faa20d2d339e6d7b80cd389f94 Mon Sep 17 00:00:00 2001 From: Nail Islamov Date: Wed, 5 Aug 2020 15:53:36 +1000 Subject: [PATCH 3/6] Fix lint error --- receiver/prometheusreceiver/internal/logger.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/receiver/prometheusreceiver/internal/logger.go b/receiver/prometheusreceiver/internal/logger.go index d7eb5f4f7c8..235037f626a 100644 --- a/receiver/prometheusreceiver/internal/logger.go +++ b/receiver/prometheusreceiver/internal/logger.go @@ -38,8 +38,9 @@ type zapToGokitLogAdapter struct { func (w *zapToGokitLogAdapter) Log(keyvals ...interface{}) error { // expecting key value pairs, the number of items need to be even if len(keyvals)%2 == 0 { - zapLevel, keyvals := extractLogLevel(keyvals) - logFunc, err := levelToFunc(w.l, zapLevel) + var lvl zapcore.Level + lvl, keyvals = extractLogLevel(keyvals) + logFunc, err := levelToFunc(w.l, lvl) if err != nil { return err } From 26be357523eb437df8e5a72fb102cf67725adf7d Mon Sep 17 00:00:00 2001 From: Nail Islamov Date: Wed, 5 Aug 2020 16:05:28 +1000 Subject: [PATCH 4/6] Improve code coverage --- receiver/prometheusreceiver/internal/logger.go | 10 ++-------- .../prometheusreceiver/internal/logger_test.go | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/receiver/prometheusreceiver/internal/logger.go b/receiver/prometheusreceiver/internal/logger.go index 235037f626a..06963b8faca 100644 --- a/receiver/prometheusreceiver/internal/logger.go +++ b/receiver/prometheusreceiver/internal/logger.go @@ -101,9 +101,9 @@ func toZapLevel(value level.Value) zapcore.Level { return zapcore.InfoLevel case "debug": return zapcore.DebugLevel - default: - return zapcore.InfoLevel } + + return zapcore.InfoLevel } // find a matching zap logging function to be used for a given level @@ -117,12 +117,6 @@ func levelToFunc(logger *zap.SugaredLogger, lvl zapcore.Level) (func(string, ... return logger.Warnf, nil case zapcore.ErrorLevel: return logger.Errorf, nil - case zapcore.DPanicLevel: - return logger.DPanicf, nil - case zapcore.PanicLevel: - return logger.Panicf, nil - case zapcore.FatalLevel: - return logger.Fatalf, nil } return nil, fmt.Errorf("unrecognized level: %q", lvl) } diff --git a/receiver/prometheusreceiver/internal/logger_test.go b/receiver/prometheusreceiver/internal/logger_test.go index ac7fc8c3c29..e90f98ad66e 100644 --- a/receiver/prometheusreceiver/internal/logger_test.go +++ b/receiver/prometheusreceiver/internal/logger_test.go @@ -41,6 +41,15 @@ func TestExtractLogLevel(t *testing.T) { wantLevel: zapcore.InfoLevel, // Default wantOutput: []interface{}{}, }, + { + name: "info level", + input: []interface{}{ + "level", + level.InfoValue(), + }, + wantLevel: zapcore.InfoLevel, + wantOutput: []interface{}{}, + }, { name: "warn level", input: []interface{}{ @@ -50,6 +59,15 @@ func TestExtractLogLevel(t *testing.T) { wantLevel: zapcore.WarnLevel, wantOutput: []interface{}{}, }, + { + name: "error level", + input: []interface{}{ + "level", + level.ErrorValue(), + }, + wantLevel: zapcore.ErrorLevel, + wantOutput: []interface{}{}, + }, { name: "debug level + extra fields", input: []interface{}{ From 0727b76a3e7ef02ce52abb10e043943bb25d2f01 Mon Sep 17 00:00:00 2001 From: Nail Islamov Date: Wed, 5 Aug 2020 23:47:47 +1000 Subject: [PATCH 5/6] Recognize message, more tests --- .../prometheusreceiver/internal/logger.go | 104 +++++++------ .../internal/logger_test.go | 147 +++++++++++++----- 2 files changed, 166 insertions(+), 85 deletions(-) diff --git a/receiver/prometheusreceiver/internal/logger.go b/receiver/prometheusreceiver/internal/logger.go index 06963b8faca..87c4b8cbf70 100644 --- a/receiver/prometheusreceiver/internal/logger.go +++ b/receiver/prometheusreceiver/internal/logger.go @@ -15,12 +15,9 @@ package internal import ( - "fmt" - gokitLog "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "go.uber.org/zap" - "go.uber.org/zap/zapcore" ) // NewZapToGokitLogAdapter create an adapter for zap.Logger to gokitLog.Logger @@ -35,16 +32,22 @@ type zapToGokitLogAdapter struct { l *zap.SugaredLogger } +type logData struct { + level level.Value + msg string + otherFields []interface{} +} + func (w *zapToGokitLogAdapter) Log(keyvals ...interface{}) error { // expecting key value pairs, the number of items need to be even if len(keyvals)%2 == 0 { - var lvl zapcore.Level - lvl, keyvals = extractLogLevel(keyvals) - logFunc, err := levelToFunc(w.l, lvl) + // Extract log level and message and log them using corresponding zap function + ld := extractLogData(keyvals) + logFunc, err := levelToFunc(w.l, ld.level) if err != nil { return err } - logFunc("", keyvals...) + logFunc(ld.msg, ld.otherFields...) } else { // in case something goes wrong w.l.Info(keyvals...) @@ -52,73 +55,80 @@ func (w *zapToGokitLogAdapter) Log(keyvals ...interface{}) error { return nil } -// extract go-kit log level from key value pairs, convert it to zap log level -// and remove from the list to avoid duplication in log message -func extractLogLevel(keyvals []interface{}) (zapcore.Level, []interface{}) { - zapLevel := zapcore.InfoLevel - output := make([]interface{}, 0, len(keyvals)) +func extractLogData(keyvals []interface{}) *logData { + lvl := level.InfoValue() // default + msg := "" + + other := make([]interface{}, 0, len(keyvals)) for i := 0; i < len(keyvals); i = i + 2 { key := keyvals[i] val := keyvals[i+1] if l, ok := matchLogLevel(key, val); ok { - zapLevel = *l + lvl = l continue } - output = append(output, key, val) + if m, ok := matchLogMessage(key, val); ok { + msg = m + continue + } + + other = append(other, key, val) } - return zapLevel, output + return &logData{ + level: lvl, + msg: msg, + otherFields: other, + } } -// check if a given key-value pair represents go-kit log level and return -// a corresponding zap log level -func matchLogLevel(key interface{}, val interface{}) (*zapcore.Level, bool) { +// check if a given key-value pair represents go-kit log message and return it +func matchLogMessage(key interface{}, val interface{}) (string, bool) { strKey, ok := key.(string) - if !ok || strKey != "level" { - return nil, false + if !ok || strKey != "msg" { + return "", false } - levelVal, ok := val.(level.Value) + msg, ok := val.(string) if !ok { - return nil, false + return "", false } - zapLevel := toZapLevel(levelVal) - return &zapLevel, true + return msg, true } -// convert go-kit log level to zap -func toZapLevel(value level.Value) zapcore.Level { - // See https://github.com/go-kit/kit/blob/556100560949062d23fe05d9fda4ce173c30c59f/log/level/level.go#L184-L187 - switch value.String() { - case "error": - return zapcore.ErrorLevel - case "warn": - return zapcore.WarnLevel - case "info": - return zapcore.InfoLevel - case "debug": - return zapcore.DebugLevel +// check if a given key-value pair represents go-kit log level and return it +func matchLogLevel(key interface{}, val interface{}) (level.Value, bool) { + strKey, ok := key.(string) + if !ok || strKey != "level" { + return nil, false + } + + levelVal, ok := val.(level.Value) + if !ok { + return nil, false } - return zapcore.InfoLevel + return levelVal, true } // find a matching zap logging function to be used for a given level -func levelToFunc(logger *zap.SugaredLogger, lvl zapcore.Level) (func(string, ...interface{}), error) { +func levelToFunc(logger *zap.SugaredLogger, lvl level.Value) (func(string, ...interface{}), error) { switch lvl { - case zapcore.DebugLevel: - return logger.Debugf, nil - case zapcore.InfoLevel: - return logger.Infof, nil - case zapcore.WarnLevel: - return logger.Warnf, nil - case zapcore.ErrorLevel: - return logger.Errorf, nil + case level.DebugValue(): + return logger.Debugw, nil + case level.InfoValue(): + return logger.Infow, nil + case level.WarnValue(): + return logger.Warnw, nil + case level.ErrorValue(): + return logger.Errorw, nil } - return nil, fmt.Errorf("unrecognized level: %q", lvl) + + // default + return logger.Infof, nil } var _ gokitLog.Logger = (*zapToGokitLogAdapter)(nil) diff --git a/receiver/prometheusreceiver/internal/logger_test.go b/receiver/prometheusreceiver/internal/logger_test.go index e90f98ad66e..47ba3046e61 100644 --- a/receiver/prometheusreceiver/internal/logger_test.go +++ b/receiver/prometheusreceiver/internal/logger_test.go @@ -17,29 +17,98 @@ package internal import ( "testing" - "github.com/bmizerany/assert" "github.com/go-kit/kit/log/level" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" "go.uber.org/zap/zapcore" ) -func TestExtractLogLevel(t *testing.T) { +func TestLog(t *testing.T) { tcs := []struct { - name string - input []interface{} - wantLevel zapcore.Level - wantOutput []interface{} + name string + input []interface{} + wantLevel zapcore.Level + wantMessage string }{ { - name: "nil fields", - input: nil, - wantLevel: zapcore.InfoLevel, // Default - wantOutput: []interface{}{}, + name: "Starting provider", + input: []interface{}{ + "level", + level.DebugValue(), + "msg", + "Starting provider", + "provider", + "string/0", + "subs", + "[target1]", + }, + wantLevel: zapcore.DebugLevel, + wantMessage: "Starting provider", + }, + { + name: "Scrape failed", + input: []interface{}{ + "level", + level.ErrorValue(), + "scrape_pool", + "target1", + "msg", + "Scrape failed", + "err", + "server returned HTTP status 500 Internal Server Error", + }, + wantLevel: zapcore.ErrorLevel, + wantMessage: "Scrape failed", + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + conf := zap.NewProductionConfig() + conf.Level.SetLevel(zapcore.DebugLevel) + + // capture zap log entry + var entry zapcore.Entry + h := func(e zapcore.Entry) error { + entry = e + return nil + } + + logger, err := conf.Build(zap.Hooks(h)) + require.NoError(t, err) + + adapter := NewZapToGokitLogAdapter(logger) + err = adapter.Log(tc.input...) + require.NoError(t, err) + + assert.Equal(t, tc.wantLevel, entry.Level) + assert.Equal(t, tc.wantMessage, entry.Message) + }) + } +} + +func TestExtractLogData(t *testing.T) { + tcs := []struct { + name string + input []interface{} + wantLevel level.Value + wantMessage string + wantOutput []interface{} + }{ + { + name: "nil fields", + input: nil, + wantLevel: level.InfoValue(), // Default + wantMessage: "", + wantOutput: []interface{}{}, }, { - name: "empty fields", - input: []interface{}{}, - wantLevel: zapcore.InfoLevel, // Default - wantOutput: []interface{}{}, + name: "empty fields", + input: []interface{}{}, + wantLevel: level.InfoValue(), // Default + wantMessage: "", + wantOutput: []interface{}{}, }, { name: "info level", @@ -47,8 +116,9 @@ func TestExtractLogLevel(t *testing.T) { "level", level.InfoValue(), }, - wantLevel: zapcore.InfoLevel, - wantOutput: []interface{}{}, + wantLevel: level.InfoValue(), + wantMessage: "", + wantOutput: []interface{}{}, }, { name: "warn level", @@ -56,8 +126,9 @@ func TestExtractLogLevel(t *testing.T) { "level", level.WarnValue(), }, - wantLevel: zapcore.WarnLevel, - wantOutput: []interface{}{}, + wantLevel: level.WarnValue(), + wantMessage: "", + wantOutput: []interface{}{}, }, { name: "error level", @@ -65,41 +136,40 @@ func TestExtractLogLevel(t *testing.T) { "level", level.ErrorValue(), }, - wantLevel: zapcore.ErrorLevel, - wantOutput: []interface{}{}, + wantLevel: level.ErrorValue(), + wantMessage: "", + wantOutput: []interface{}{}, }, { name: "debug level + extra fields", input: []interface{}{ - "ts", - 1596604719.955769, + "timestamp", + 1596604719, "level", level.DebugValue(), "msg", "http client error", }, - wantLevel: zapcore.DebugLevel, + wantLevel: level.DebugValue(), + wantMessage: "http client error", wantOutput: []interface{}{ - "ts", - 1596604719.955769, - "msg", - "http client error", + "timestamp", + 1596604719, }, }, { name: "missing level field", input: []interface{}{ - "ts", - 1596604719.955769, + "timestamp", + 1596604719, "msg", "http client error", }, - wantLevel: zapcore.InfoLevel, // Default + wantLevel: level.InfoValue(), // Default + wantMessage: "http client error", wantOutput: []interface{}{ - "ts", - 1596604719.955769, - "msg", - "http client error", + "timestamp", + 1596604719, }, }, { @@ -108,7 +178,7 @@ func TestExtractLogLevel(t *testing.T) { "level", "warn", // String is not recognized }, - wantLevel: zapcore.InfoLevel, // Default + wantLevel: level.InfoValue(), // Default wantOutput: []interface{}{ "level", "warn", // Field is preserved @@ -118,9 +188,10 @@ func TestExtractLogLevel(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - zapLevel, output := extractLogLevel(tc.input) - assert.Equal(t, tc.wantLevel, zapLevel) - assert.Equal(t, tc.wantOutput, output) + ld := extractLogData(tc.input) + assert.Equal(t, tc.wantLevel, ld.level) + assert.Equal(t, tc.wantMessage, ld.msg) + assert.Equal(t, tc.wantOutput, ld.otherFields) }) } } From 427557ed71b701225604439118a7aab3839b4b16 Mon Sep 17 00:00:00 2001 From: Nail Islamov Date: Wed, 5 Aug 2020 23:51:06 +1000 Subject: [PATCH 6/6] Define constants --- receiver/prometheusreceiver/internal/logger.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/receiver/prometheusreceiver/internal/logger.go b/receiver/prometheusreceiver/internal/logger.go index 87c4b8cbf70..6917331e214 100644 --- a/receiver/prometheusreceiver/internal/logger.go +++ b/receiver/prometheusreceiver/internal/logger.go @@ -20,6 +20,11 @@ import ( "go.uber.org/zap" ) +const ( + levelKey = "level" + msgKey = "msg" +) + // NewZapToGokitLogAdapter create an adapter for zap.Logger to gokitLog.Logger func NewZapToGokitLogAdapter(logger *zap.Logger) gokitLog.Logger { // need to skip two levels in order to get the correct caller @@ -87,7 +92,7 @@ func extractLogData(keyvals []interface{}) *logData { // check if a given key-value pair represents go-kit log message and return it func matchLogMessage(key interface{}, val interface{}) (string, bool) { strKey, ok := key.(string) - if !ok || strKey != "msg" { + if !ok || strKey != msgKey { return "", false } @@ -102,7 +107,7 @@ func matchLogMessage(key interface{}, val interface{}) (string, bool) { // check if a given key-value pair represents go-kit log level and return it func matchLogLevel(key interface{}, val interface{}) (level.Value, bool) { strKey, ok := key.(string) - if !ok || strKey != "level" { + if !ok || strKey != levelKey { return nil, false }