diff --git a/changelog/@unreleased/pr-130.v2.yml b/changelog/@unreleased/pr-130.v2.yml new file mode 100644 index 00000000..3d47849b --- /dev/null +++ b/changelog/@unreleased/pr-130.v2.yml @@ -0,0 +1,5 @@ +type: improvement +improvement: + description: Add metric1log wrapped logger + links: + - https://github.com/palantir/witchcraft-go-logging/pull/130 diff --git a/wlog-glog/logger_test.go b/wlog-glog/logger_test.go index 8d50a9fd..216d7621 100644 --- a/wlog-glog/logger_test.go +++ b/wlog-glog/logger_test.go @@ -271,6 +271,29 @@ func TestWrapped1Audit2Log(t *testing.T) { } } +func TestWrapped1Metric1Log(t *testing.T) { + os.Args = []string{ + os.Args[0], + "-logtostderr=true", + } + flag.Parse() + + entityName := "entity" + entityVersion := "version" + for _, tc := range wrapped1logtests.Metric1TestCases(entityName, entityVersion) { + // TODO: test output + logger := wrapped1log.NewFromProvider( + os.Stdout, + wlog.InfoLevel, + wlogglog.LoggerProvider(), + entityName, + entityVersion, + ).Metric() + + logger.Metric(tc.MetricName, tc.MetricType, tc.Params()...) + } +} + func TestWrapped1Log(t *testing.T) { os.Args = []string{ os.Args[0], diff --git a/wlog-zap/logger_test.go b/wlog-zap/logger_test.go index 7dcdf3e9..2290f16d 100644 --- a/wlog-zap/logger_test.go +++ b/wlog-zap/logger_test.go @@ -134,6 +134,14 @@ func TestWrapped1LogEvt2Log(t *testing.T) { }) } +func TestWrapped1Metric1Log(t *testing.T) { + entityName := "entity" + entityVersion := "version" + wrapped1logtests.Metric1LogJSONTestSuite(t, entityName, entityVersion, func(w io.Writer) metric1log.Logger { + return wrapped1log.NewFromProvider(w, wlog.InfoLevel, zapimpl.LoggerProvider(), entityName, entityVersion).Metric() + }) +} + func TestWrapped1LogSvc1Log(t *testing.T) { entityName := "entity" entityVersion := "version" diff --git a/wlog-zerolog/logger_test.go b/wlog-zerolog/logger_test.go index 0e3405ca..a088fd15 100644 --- a/wlog-zerolog/logger_test.go +++ b/wlog-zerolog/logger_test.go @@ -134,6 +134,14 @@ func TestWrapped1Evt2Log(t *testing.T) { }) } +func TestWrapped1Metric1Log(t *testing.T) { + entityName := "entity" + entityVersion := "version" + wrapped1logtests.Metric1LogJSONTestSuite(t, entityName, entityVersion, func(w io.Writer) metric1log.Logger { + return wrapped1log.NewFromProvider(w, wlog.InfoLevel, wlogzerolog.LoggerProvider(), entityName, entityVersion).Metric() + }) +} + func TestWrapped1Log(t *testing.T) { entityName := "entity" entityVersion := "version" diff --git a/wlog/metriclog/metric1log/logger_default.go b/wlog/metriclog/metric1log/logger_default.go index 2b19454f..85336212 100644 --- a/wlog/metriclog/metric1log/logger_default.go +++ b/wlog/metriclog/metric1log/logger_default.go @@ -25,10 +25,10 @@ type defaultLogger struct { } func (l *defaultLogger) Metric(name, typ string, params ...Param) { - l.logger.Log(toParams(name, typ, params)...) + l.logger.Log(ToParams(name, typ, params)...) } -func toParams(metricName, metricType string, inParams []Param) []wlog.Param { +func ToParams(metricName, metricType string, inParams []Param) []wlog.Param { outParams := make([]wlog.Param, len(defaultTypeParam)+1+len(inParams)) copy(outParams, defaultTypeParam) outParams[len(defaultTypeParam)] = wlog.NewParam(metricNameTypeParam(metricName, metricType).apply) diff --git a/wlog/wrappedlog/wrapped1log/logger.go b/wlog/wrappedlog/wrapped1log/logger.go index 81e1fad5..8e236c03 100644 --- a/wlog/wrappedlog/wrapped1log/logger.go +++ b/wlog/wrappedlog/wrapped1log/logger.go @@ -21,6 +21,7 @@ import ( "github.com/palantir/witchcraft-go-logging/wlog/auditlog/audit2log" "github.com/palantir/witchcraft-go-logging/wlog/diaglog/diag1log" "github.com/palantir/witchcraft-go-logging/wlog/evtlog/evt2log" + "github.com/palantir/witchcraft-go-logging/wlog/metriclog/metric1log" "github.com/palantir/witchcraft-go-logging/wlog/svclog/svc1log" "github.com/palantir/witchcraft-go-logging/wlog/trclog/trc1log" ) @@ -29,6 +30,7 @@ type Logger interface { Audit() audit2log.Logger Diagnostic() diag1log.Logger Event() evt2log.Logger + Metric() metric1log.Logger Service(params ...svc1log.Param) svc1log.Logger Trace() trc1log.Logger } diff --git a/wlog/wrappedlog/wrapped1log/logger_default.go b/wlog/wrappedlog/wrapped1log/logger_default.go index 95b76676..29ca1865 100644 --- a/wlog/wrappedlog/wrapped1log/logger_default.go +++ b/wlog/wrappedlog/wrapped1log/logger_default.go @@ -19,6 +19,7 @@ import ( "github.com/palantir/witchcraft-go-logging/wlog/auditlog/audit2log" "github.com/palantir/witchcraft-go-logging/wlog/diaglog/diag1log" "github.com/palantir/witchcraft-go-logging/wlog/evtlog/evt2log" + "github.com/palantir/witchcraft-go-logging/wlog/metriclog/metric1log" "github.com/palantir/witchcraft-go-logging/wlog/svclog/svc1log" "github.com/palantir/witchcraft-go-logging/wlog/trclog/trc1log" ) @@ -54,6 +55,14 @@ func (l *defaultLogger) Event() evt2log.Logger { } } +func (l *defaultLogger) Metric() metric1log.Logger { + return &wrappedMetric1Logger{ + name: l.name, + version: l.version, + logger: l.logger, + } +} + func (l *defaultLogger) Service(params ...svc1log.Param) svc1log.Logger { return &wrappedSvc1Logger{ params: params, diff --git a/wlog/wrappedlog/wrapped1log/logger_metric.go b/wlog/wrappedlog/wrapped1log/logger_metric.go new file mode 100644 index 00000000..5283e5f7 --- /dev/null +++ b/wlog/wrappedlog/wrapped1log/logger_metric.go @@ -0,0 +1,39 @@ +// Copyright (c) 2021 Palantir Technologies. All rights reserved. +// +// 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 wrapped1log + +import ( + "github.com/palantir/witchcraft-go-logging/wlog" + "github.com/palantir/witchcraft-go-logging/wlog/metriclog/metric1log" +) + +type wrappedMetric1Logger struct { + name string + version string + + logger wlog.Logger +} + +func (l *wrappedMetric1Logger) Metric(name, typ string, params ...metric1log.Param) { + l.logger.Log(l.toMetricParams(name, typ, params)...) +} + +func (l *wrappedMetric1Logger) toMetricParams(metricName, metricType string, inParams []metric1log.Param) []wlog.Param { + outParams := make([]wlog.Param, len(defaultTypeParam)+2) + copy(outParams, defaultTypeParam) + outParams[len(defaultTypeParam)] = wlog.NewParam(wrappedTypeParams(l.name, l.version).apply) + outParams[len(defaultTypeParam)+1] = wlog.NewParam(metric1PayloadParams(metricName, metricType, inParams).apply) + return outParams +} diff --git a/wlog/wrappedlog/wrapped1log/params.go b/wlog/wrappedlog/wrapped1log/params.go index ed62baa4..cc3788ed 100644 --- a/wlog/wrappedlog/wrapped1log/params.go +++ b/wlog/wrappedlog/wrapped1log/params.go @@ -20,6 +20,7 @@ import ( "github.com/palantir/witchcraft-go-logging/wlog/auditlog/audit2log" "github.com/palantir/witchcraft-go-logging/wlog/diaglog/diag1log" "github.com/palantir/witchcraft-go-logging/wlog/evtlog/evt2log" + "github.com/palantir/witchcraft-go-logging/wlog/metriclog/metric1log" "github.com/palantir/witchcraft-go-logging/wlog/svclog/svc1log" "github.com/palantir/witchcraft-go-logging/wlog/trclog/trc1log" "github.com/palantir/witchcraft-go-tracing/wtracing" @@ -95,6 +96,18 @@ func evt2PayloadParams(name string, params []evt2log.Param) Param { }) } +func metric1PayloadParams(metricName, metricType string, params []metric1log.Param) Param { + return paramFunc(func(entry wlog.LogEntry) { + metric1Log := wlog.NewMapLogEntry() + wlog.ApplyParams(metric1Log, metric1log.ToParams(metricName, metricType, params)) + payload := wlog.NewMapLogEntry() + payload.StringValue(PayloadTypeKey, PayloadMetricLogV1) + payload.AnyMapValue(PayloadMetricLogV1, metric1Log.AllValues()) + + entry.AnyMapValue(PayloadKey, payload.AllValues()) + }) +} + func svc1PayloadParams(message string, level wlog.Param, params []svc1log.Param) Param { return paramFunc(func(entry wlog.LogEntry) { svc1Log := wlog.NewMapLogEntry() diff --git a/wlog/wrappedlog/wrapped1log/wrapped1logtests/evt2log_tests.go b/wlog/wrappedlog/wrapped1log/wrapped1logtests/evt2log_tests.go index de5da8c7..f4ba2c42 100644 --- a/wlog/wrappedlog/wrapped1log/wrapped1logtests/evt2log_tests.go +++ b/wlog/wrappedlog/wrapped1log/wrapped1logtests/evt2log_tests.go @@ -93,7 +93,7 @@ func Evt2TestCases(entityName, entityVersion string) []Evt2TestCase { func Evt2LogJSONTestSuite(t *testing.T, entityName, entityVersion string, loggerProvider func(w io.Writer) evt2log.Logger) { evt2LogJSONOutputTests(t, entityName, entityVersion, loggerProvider) - valueIsntOverwrittenByValues(t, entityName, entityVersion, loggerProvider) + evt2LogValueIsntOverwrittenByValues(t, entityName, entityVersion, loggerProvider) extraValuesIndependentAcrossCalls(t, entityName, entityVersion, loggerProvider) } @@ -117,7 +117,7 @@ func evt2LogJSONOutputTests(t *testing.T, entityName, entityVersion string, logg // Verifies that if different parameters are specified using Value and Values params, all of the values are present in // the final output (that is, these parameters should be additive). -func valueIsntOverwrittenByValues(t *testing.T, entityName, entityVersion string, loggerProvider func(w io.Writer) evt2log.Logger) { +func evt2LogValueIsntOverwrittenByValues(t *testing.T, entityName, entityVersion string, loggerProvider func(w io.Writer) evt2log.Logger) { t.Run("Value and Values params are additive", func(t *testing.T) { var buf bytes.Buffer logger := loggerProvider(&buf) diff --git a/wlog/wrappedlog/wrapped1log/wrapped1logtests/metric1log_tests.go b/wlog/wrappedlog/wrapped1log/wrapped1logtests/metric1log_tests.go new file mode 100644 index 00000000..d7d5b147 --- /dev/null +++ b/wlog/wrappedlog/wrapped1log/wrapped1logtests/metric1log_tests.go @@ -0,0 +1,326 @@ +// Copyright (c) 2021 Palantir Technologies. All rights reserved. +// +// 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 wrapped1logtests + +import ( + "bytes" + "encoding/json" + "io" + "testing" + + "github.com/palantir/pkg/objmatcher" + "github.com/palantir/pkg/safejson" + "github.com/palantir/witchcraft-go-logging/wlog/metriclog/metric1log" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type Metric1TestCase struct { + Name string + MetricName string + MetricType string + Tags map[string]string + Values map[string]interface{} + UID string + SID string + TokenID string + UnsafeParams map[string]interface{} + JSONMatcher objmatcher.MapMatcher +} + +func (tc Metric1TestCase) Params() []metric1log.Param { + return []metric1log.Param{ + metric1log.Values(tc.Values), + metric1log.UID(tc.UID), + metric1log.SID(tc.SID), + metric1log.TokenID(tc.TokenID), + metric1log.Tags(tc.Tags), + metric1log.UnsafeParams(tc.UnsafeParams), + } +} + +func Metric1TestCases(entityName, entityVersion string) []Metric1TestCase { + return []Metric1TestCase{ + { + Name: "basic metric log entry", + MetricName: "com.palantir.deployability.logtrough.iteratorage.millis", + MetricType: "histogram", + UID: "user-1", + SID: "session-1", + Values: map[string]interface{}{ + "max": 1400, + "mean": 70, + "stddev": 20, + "p95": 100, + "p99": 1200, + "p999": 1350, + "count": 100, + }, + Tags: map[string]string{ + "shardId": "shard-1234", + }, + TokenID: "X-Y-Z", + UnsafeParams: map[string]interface{}{ + "Password": "HelloWorld!", + }, + JSONMatcher: objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "type": objmatcher.NewEqualsMatcher("wrapped.1"), + "entityName": objmatcher.NewEqualsMatcher(entityName), + "entityVersion": objmatcher.NewEqualsMatcher(entityVersion), + "payload": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "type": objmatcher.NewEqualsMatcher("metricLogV1"), + "metricLogV1": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "metricName": objmatcher.NewEqualsMatcher("com.palantir.deployability.logtrough.iteratorage.millis"), + "time": objmatcher.NewRegExpMatcher(".+"), + "type": objmatcher.NewEqualsMatcher("metric.1"), + "metricType": objmatcher.NewEqualsMatcher("histogram"), + "uid": objmatcher.NewEqualsMatcher("user-1"), + "sid": objmatcher.NewEqualsMatcher("session-1"), + "values": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "max": objmatcher.NewEqualsMatcher(json.Number("1400")), + "mean": objmatcher.NewEqualsMatcher(json.Number("70")), + "stddev": objmatcher.NewEqualsMatcher(json.Number("20")), + "p95": objmatcher.NewEqualsMatcher(json.Number("100")), + "p99": objmatcher.NewEqualsMatcher(json.Number("1200")), + "p999": objmatcher.NewEqualsMatcher(json.Number("1350")), + "count": objmatcher.NewEqualsMatcher(json.Number("100")), + }), + "tags": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "shardId": objmatcher.NewEqualsMatcher("shard-1234"), + }), + "tokenId": objmatcher.NewEqualsMatcher("X-Y-Z"), + "unsafeParams": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "Password": objmatcher.NewEqualsMatcher("HelloWorld!"), + }), + }), + }), + }), + }, + } +} + +func Metric1LogJSONTestSuite(t *testing.T, entityName, entityVersion string, loggerProvider func(w io.Writer) metric1log.Logger) { + metric1LogJSONOutputTests(t, entityName, entityVersion, loggerProvider) + metric1LogValueIsntOverwrittenByValues(t, entityName, entityVersion, loggerProvider) + extraValuesDoNotAppear(t, entityName, entityVersion, loggerProvider) + tagIsntOverwrittenByTags(t, entityName, entityVersion, loggerProvider) + extraTagsDoNotAppear(t, entityName, entityVersion, loggerProvider) +} + +func metric1LogJSONOutputTests(t *testing.T, entityName, entityVersion string, loggerProvider func(w io.Writer) metric1log.Logger) { + for i, tc := range Metric1TestCases(entityName, entityVersion) { + t.Run(tc.Name, func(t *testing.T) { + buf := &bytes.Buffer{} + logger := loggerProvider(buf) + + logger.Metric(tc.MetricName, tc.MetricType, tc.Params()...) + + gotMetricLog := map[string]interface{}{} + logEntry := buf.Bytes() + err := safejson.Unmarshal(logEntry, &gotMetricLog) + require.NoError(t, err, "Case %d: %s\nMetric log line is not a valid map: %v", i, tc.Name, string(logEntry)) + + assert.NoError(t, tc.JSONMatcher.Matches(gotMetricLog), "Case %d: %s", i, tc.Name) + }) + } +} + +// Verifies that if different parameters are specified using Value and Values params, all of the values are present in +// the final output (that is, these parameters should be additive). +func metric1LogValueIsntOverwrittenByValues(t *testing.T, entityName, entityVersion string, loggerProvider func(w io.Writer) metric1log.Logger) { + t.Run("Value and Values params are additive", func(t *testing.T) { + var buf bytes.Buffer + logger := loggerProvider(&buf) + + logger.Metric("metric", "metric-type", metric1log.Value("key", "value"), metric1log.Values(map[string]interface{}{"keys": "values"})) + + gotMetricLog := map[string]interface{}{} + logEntry := buf.Bytes() + err := safejson.Unmarshal(logEntry, &gotMetricLog) + require.NoError(t, err, "Metric log line is not a valid map: %v", string(logEntry)) + assert.NoError(t, objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "type": objmatcher.NewEqualsMatcher("wrapped.1"), + "entityName": objmatcher.NewEqualsMatcher(entityName), + "entityVersion": objmatcher.NewEqualsMatcher(entityVersion), + "payload": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "type": objmatcher.NewEqualsMatcher("metricLogV1"), + "metricLogV1": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "metricName": objmatcher.NewEqualsMatcher("metric"), + "time": objmatcher.NewRegExpMatcher(".+"), + "type": objmatcher.NewEqualsMatcher("metric.1"), + "metricType": objmatcher.NewEqualsMatcher("metric-type"), + "values": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "key": objmatcher.NewEqualsMatcher("value"), + "keys": objmatcher.NewEqualsMatcher("values"), + }), + }), + }), + }).Matches(gotMetricLog)) + }) +} + +// Verifies that parameters remain separate between different logger calls (ensures there is not a bug where parameters +// are modified by making a logger call). +func extraValuesDoNotAppear(t *testing.T, entityName, entityVersion string, loggerProvider func(w io.Writer) metric1log.Logger) { + t.Run("Value and Values params stay separate across logger calls", func(t *testing.T) { + var buf bytes.Buffer + logger := loggerProvider(&buf) + + reusedParams := metric1log.Values(map[string]interface{}{"keys": "values"}) + logger.Metric("metric", "metric-type", reusedParams, metric1log.Value("key", "value")) + + gotMetricLog := map[string]interface{}{} + logEntry := buf.Bytes() + err := safejson.Unmarshal(logEntry, &gotMetricLog) + require.NoError(t, err, "Metric log line is not a valid map: %v", string(logEntry)) + assert.NoError(t, objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "type": objmatcher.NewEqualsMatcher("wrapped.1"), + "entityName": objmatcher.NewEqualsMatcher(entityName), + "entityVersion": objmatcher.NewEqualsMatcher(entityVersion), + "payload": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "type": objmatcher.NewEqualsMatcher("metricLogV1"), + "metricLogV1": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "metricName": objmatcher.NewEqualsMatcher("metric"), + "time": objmatcher.NewRegExpMatcher(".+"), + "type": objmatcher.NewEqualsMatcher("metric.1"), + "metricType": objmatcher.NewEqualsMatcher("metric-type"), + "values": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "key": objmatcher.NewEqualsMatcher("value"), + "keys": objmatcher.NewEqualsMatcher("values"), + }), + }), + }), + }).Matches(gotMetricLog)) + + buf.Reset() + logger.Metric("metric", "metric-type", reusedParams) + + gotMetricLog = map[string]interface{}{} + logEntry = buf.Bytes() + err = safejson.Unmarshal(logEntry, &gotMetricLog) + require.NoError(t, err, "Metric log line is not a valid map: %v", string(logEntry)) + assert.NoError(t, objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "type": objmatcher.NewEqualsMatcher("wrapped.1"), + "entityName": objmatcher.NewEqualsMatcher(entityName), + "entityVersion": objmatcher.NewEqualsMatcher(entityVersion), + "payload": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "type": objmatcher.NewEqualsMatcher("metricLogV1"), + "metricLogV1": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "metricName": objmatcher.NewEqualsMatcher("metric"), + "time": objmatcher.NewRegExpMatcher(".+"), + "type": objmatcher.NewEqualsMatcher("metric.1"), + "metricType": objmatcher.NewEqualsMatcher("metric-type"), + "values": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "keys": objmatcher.NewEqualsMatcher("values"), + }), + }), + }), + }).Matches(gotMetricLog)) + }) +} + +// Verifies that if different parameters are specified using Tag and Tags params, all of the tags are present in the +// final output (that is, these parameters should be additive). +func tagIsntOverwrittenByTags(t *testing.T, entityName, entityVersion string, loggerProvider func(w io.Writer) metric1log.Logger) { + t.Run("Tag and Tags params are additive", func(t *testing.T) { + var buf bytes.Buffer + logger := loggerProvider(&buf) + + logger.Metric("metric", "metric-type", metric1log.Tag("key", "value"), metric1log.Tags(map[string]string{"keys": "values"})) + + gotMetricLog := map[string]interface{}{} + logEntry := buf.Bytes() + err := safejson.Unmarshal(logEntry, &gotMetricLog) + require.NoError(t, err, "Metric log line is not a valid map: %v", string(logEntry)) + assert.NoError(t, objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "type": objmatcher.NewEqualsMatcher("wrapped.1"), + "entityName": objmatcher.NewEqualsMatcher(entityName), + "entityVersion": objmatcher.NewEqualsMatcher(entityVersion), + "payload": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "type": objmatcher.NewEqualsMatcher("metricLogV1"), + "metricLogV1": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "metricName": objmatcher.NewEqualsMatcher("metric"), + "time": objmatcher.NewRegExpMatcher(".+"), + "type": objmatcher.NewEqualsMatcher("metric.1"), + "metricType": objmatcher.NewEqualsMatcher("metric-type"), + "tags": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "key": objmatcher.NewEqualsMatcher("value"), + "keys": objmatcher.NewEqualsMatcher("values"), + }), + }), + }), + }).Matches(gotMetricLog)) + }) +} + +// Verifies that parameters remain separate between different logger calls (ensures there is not a bug where parameters +// are modified by making a logger call). +func extraTagsDoNotAppear(t *testing.T, entityName, entityVersion string, loggerProvider func(w io.Writer) metric1log.Logger) { + t.Run("Tag and Tags params stay separate across logger calls", func(t *testing.T) { + var buf bytes.Buffer + logger := loggerProvider(&buf) + + reusedParams := metric1log.Tags(map[string]string{"keys": "values"}) + logger.Metric("metric", "metric-type", reusedParams, metric1log.Tag("key", "value")) + + gotMetricLog := map[string]interface{}{} + logEntry := buf.Bytes() + err := safejson.Unmarshal(logEntry, &gotMetricLog) + require.NoError(t, err, "Metric log line is not a valid map: %v", string(logEntry)) + assert.NoError(t, objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "type": objmatcher.NewEqualsMatcher("wrapped.1"), + "entityName": objmatcher.NewEqualsMatcher(entityName), + "entityVersion": objmatcher.NewEqualsMatcher(entityVersion), + "payload": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "type": objmatcher.NewEqualsMatcher("metricLogV1"), + "metricLogV1": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "metricName": objmatcher.NewEqualsMatcher("metric"), + "time": objmatcher.NewRegExpMatcher(".+"), + "type": objmatcher.NewEqualsMatcher("metric.1"), + "metricType": objmatcher.NewEqualsMatcher("metric-type"), + "tags": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "key": objmatcher.NewEqualsMatcher("value"), + "keys": objmatcher.NewEqualsMatcher("values"), + }), + }), + }), + }).Matches(gotMetricLog)) + + buf.Reset() + logger.Metric("metric", "metric-type", reusedParams) + + gotMetricLog = map[string]interface{}{} + logEntry = buf.Bytes() + err = safejson.Unmarshal(logEntry, &gotMetricLog) + require.NoError(t, err, "Metric log line is not a valid map: %v", string(logEntry)) + assert.NoError(t, objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "type": objmatcher.NewEqualsMatcher("wrapped.1"), + "entityName": objmatcher.NewEqualsMatcher(entityName), + "entityVersion": objmatcher.NewEqualsMatcher(entityVersion), + "payload": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "type": objmatcher.NewEqualsMatcher("metricLogV1"), + "metricLogV1": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "metricName": objmatcher.NewEqualsMatcher("metric"), + "time": objmatcher.NewRegExpMatcher(".+"), + "type": objmatcher.NewEqualsMatcher("metric.1"), + "metricType": objmatcher.NewEqualsMatcher("metric-type"), + "tags": objmatcher.MapMatcher(map[string]objmatcher.Matcher{ + "keys": objmatcher.NewEqualsMatcher("values"), + }), + }), + }), + }).Matches(gotMetricLog)) + }) +}