diff --git a/app.go b/app.go index 0e81d775..2bee9c91 100644 --- a/app.go +++ b/app.go @@ -514,6 +514,15 @@ func GetDefaultLoggerFromCtx(ctx context.Context) logger.Logger { return ctx.Value(constants.LoggerCtxKey).(logger.Logger) } +// AddMetricTagsToPropagateCtx adds a key and metric tags that will +// be propagated through RPC calls +func AddMetricTagsToPropagateCtx( + ctx context.Context, + tags map[string]string, +) context.Context { + return pcontext.AddToPropagateCtx(ctx, constants.MetricTagsKey, tags) +} + // AddToPropagateCtx adds a key and value that will be propagated through RPC calls func AddToPropagateCtx(ctx context.Context, key string, val interface{}) context.Context { return pcontext.AddToPropagateCtx(ctx, key, val) diff --git a/app_test.go b/app_test.go index 12f2fe35..4bca2fec 100644 --- a/app_test.go +++ b/app_test.go @@ -462,6 +462,18 @@ func TestGetSessionFromCtx(t *testing.T) { assert.Equal(t, ss, s) } +func TestAddMetricTagsToPropagateCtx(t *testing.T) { + ctx := AddMetricTagsToPropagateCtx(context.Background(), map[string]string{ + "key": "value", + }) + val := ctx.Value(constants.PropagateCtxKey) + assert.Equal(t, map[string]interface{}{ + constants.MetricTagsKey: map[string]string{ + "key": "value", + }, + }, val) +} + func TestAddToPropagateCtx(t *testing.T) { ctx := AddToPropagateCtx(context.Background(), "key", "val") val := ctx.Value(constants.PropagateCtxKey) diff --git a/constants/const.go b/constants/const.go index 1250a274..895141fb 100644 --- a/constants/const.go +++ b/constants/const.go @@ -70,3 +70,7 @@ var StartTimeKey = "req-start-time" // RouteKey is the key holding the request route to be sent over the context var RouteKey = "req-route" + +// MetricTagsKey is the key holding request tags to be sent over the context +// to be reported +var MetricTagsKey = "metric-tags" diff --git a/metrics/report.go b/metrics/report.go index 8d7589af..6f120b44 100644 --- a/metrics/report.go +++ b/metrics/report.go @@ -39,11 +39,11 @@ func ReportTimingFromCtx(ctx context.Context, reporters []Reporter, typ string, startTime := pcontext.GetFromPropagateCtx(ctx, constants.StartTimeKey) route := pcontext.GetFromPropagateCtx(ctx, constants.RouteKey) elapsed := time.Since(time.Unix(0, startTime.(int64))) - tags := map[string]string{ + tags := getTags(ctx, map[string]string{ "route": route.(string), "status": status, "type": typ, - } + }) for _, r := range reporters { r.ReportSummary(ResponseTime, tags, float64(elapsed.Nanoseconds())) } @@ -56,10 +56,10 @@ func ReportMessageProcessDelayFromCtx(ctx context.Context, reporters []Reporter, startTime := pcontext.GetFromPropagateCtx(ctx, constants.StartTimeKey) elapsed := time.Since(time.Unix(0, startTime.(int64))) route := pcontext.GetFromPropagateCtx(ctx, constants.RouteKey) - tags := map[string]string{ + tags := getTags(ctx, map[string]string{ "route": route.(string), "type": typ, - } + }) for _, r := range reporters { r.ReportSummary(ProcessDelay, tags, float64(elapsed.Nanoseconds())) } @@ -89,3 +89,25 @@ func ReportSysMetrics(reporters []Reporter, period time.Duration) { time.Sleep(period) } } + +func tagsFromContext(ctx context.Context) map[string]string { + val := pcontext.GetFromPropagateCtx(ctx, constants.MetricTagsKey) + if val == nil { + return map[string]string{} + } + + tags, ok := val.(map[string]string) + if !ok { + return map[string]string{} + } + + return tags +} + +func getTags(ctx context.Context, tags map[string]string) map[string]string { + for k, v := range tagsFromContext(ctx) { + tags[k] = v + } + + return tags +} diff --git a/metrics/report_test.go b/metrics/report_test.go index 39e1249a..3a8697d4 100644 --- a/metrics/report_test.go +++ b/metrics/report_test.go @@ -34,23 +34,103 @@ import ( ) func TestReportTimingFromCtx(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockMetricsReporter := mocks.NewMockReporter(ctrl) - - originalTs := time.Now().UnixNano() - expectedRoute := uuid.New().String() - expectedType := uuid.New().String() - expectedErrored := true - ctx := pcontext.AddToPropagateCtx(context.Background(), constants.StartTimeKey, originalTs) - ctx = pcontext.AddToPropagateCtx(ctx, constants.RouteKey, expectedRoute) - - time.Sleep(200 * time.Millisecond) // to test duration report - mockMetricsReporter.EXPECT().ReportSummary(ResponseTime, gomock.Any(), gomock.Any()).Do( - func(metric string, tags map[string]string, duration float64) { - assert.InDelta(t, duration, time.Now().UnixNano()-originalTs, 10e6) - }, - ) - - ReportTimingFromCtx(ctx, []Reporter{mockMetricsReporter}, expectedType, expectedErrored) + t.Run("test-duration", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockMetricsReporter := mocks.NewMockReporter(ctrl) + + originalTs := time.Now().UnixNano() + expectedRoute := uuid.New().String() + expectedType := uuid.New().String() + expectedErrored := true + ctx := pcontext.AddToPropagateCtx(context.Background(), constants.StartTimeKey, originalTs) + ctx = pcontext.AddToPropagateCtx(ctx, constants.RouteKey, expectedRoute) + + time.Sleep(200 * time.Millisecond) // to test duration report + mockMetricsReporter.EXPECT().ReportSummary(ResponseTime, gomock.Any(), gomock.Any()).Do( + func(metric string, tags map[string]string, duration float64) { + assert.InDelta(t, duration, time.Now().UnixNano()-originalTs, 10e6) + }, + ) + + ReportTimingFromCtx(ctx, []Reporter{mockMetricsReporter}, expectedType, expectedErrored) + }) + + t.Run("test-tags", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockMetricsReporter := mocks.NewMockReporter(ctrl) + + originalTs := time.Now().UnixNano() + expectedRoute := uuid.New().String() + expectedType := uuid.New().String() + expectedErrored := false + ctx := pcontext.AddToPropagateCtx(context.Background(), constants.StartTimeKey, originalTs) + ctx = pcontext.AddToPropagateCtx(ctx, constants.RouteKey, expectedRoute) + ctx = pcontext.AddToPropagateCtx(ctx, constants.MetricTagsKey, map[string]string{ + "key": "value", + }) + + expectedTags := map[string]string{ + "route": expectedRoute, + "status": "ok", + "type": expectedType, + "key": "value", + } + + mockMetricsReporter.EXPECT().ReportSummary(ResponseTime, expectedTags, gomock.Any()) + + ReportTimingFromCtx(ctx, []Reporter{mockMetricsReporter}, expectedType, expectedErrored) + }) + + t.Run("test-tags-not-correct-type", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockMetricsReporter := mocks.NewMockReporter(ctrl) + + originalTs := time.Now().UnixNano() + expectedRoute := uuid.New().String() + expectedType := uuid.New().String() + expectedErrored := false + ctx := pcontext.AddToPropagateCtx(context.Background(), constants.StartTimeKey, originalTs) + ctx = pcontext.AddToPropagateCtx(ctx, constants.RouteKey, expectedRoute) + ctx = pcontext.AddToPropagateCtx(ctx, constants.MetricTagsKey, "not-map") + + expectedTags := map[string]string{ + "route": expectedRoute, + "status": "ok", + "type": expectedType, + } + + mockMetricsReporter.EXPECT().ReportSummary(ResponseTime, expectedTags, gomock.Any()) + + ReportTimingFromCtx(ctx, []Reporter{mockMetricsReporter}, expectedType, expectedErrored) + }) +} + +func TestReportMessageProcessDelayFromCtx(t *testing.T) { + t.Run("test-tags", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockMetricsReporter := mocks.NewMockReporter(ctrl) + + originalTs := time.Now().UnixNano() + expectedRoute := uuid.New().String() + expectedType := uuid.New().String() + ctx := pcontext.AddToPropagateCtx(context.Background(), constants.StartTimeKey, originalTs) + ctx = pcontext.AddToPropagateCtx(ctx, constants.RouteKey, expectedRoute) + ctx = pcontext.AddToPropagateCtx(ctx, constants.MetricTagsKey, map[string]string{ + "key": "value", + }) + + expectedTags := map[string]string{ + "route": expectedRoute, + "type": expectedType, + "key": "value", + } + + mockMetricsReporter.EXPECT().ReportSummary(ProcessDelay, expectedTags, gomock.Any()) + + ReportMessageProcessDelayFromCtx(ctx, []Reporter{mockMetricsReporter}, expectedType) + }) }