diff --git a/docs/DATA_FORMATS_INPUT.md b/docs/DATA_FORMATS_INPUT.md index 64097e7d6..c1192e72b 100644 --- a/docs/DATA_FORMATS_INPUT.md +++ b/docs/DATA_FORMATS_INPUT.md @@ -483,7 +483,7 @@ You can also change the path to the typesdb or add additional typesdb using # Dropwizard: -The dropwizard format can parse the JSON representation of a single dropwizard metric registry. By default, tags are parsed from metric names as if they were actual influxdb line protocol keys (`measurement<,tag_set>`) which can be overriden by defining custom [measurement & tag templates](./DATA_FORMATS_INPUT.md#measurement--tag-templates). All field values are collected as float64 fields. +The dropwizard format can parse the JSON representation of a single dropwizard metric registry. By default, tags are parsed from metric names as if they were actual influxdb line protocol keys (`measurement<,tag_set>`) which can be overriden by defining custom [measurement & tag templates](./DATA_FORMATS_INPUT.md#measurement--tag-templates). All field value types are supported, `string`, `number` and `boolean`. A typical JSON of a dropwizard metric registry: diff --git a/plugins/parsers/dropwizard/parser.go b/plugins/parsers/dropwizard/parser.go index dc5442068..4c07ca286 100644 --- a/plugins/parsers/dropwizard/parser.go +++ b/plugins/parsers/dropwizard/parser.go @@ -14,6 +14,9 @@ import ( "github.com/tidwall/gjson" ) +var fieldEscaper = strings.NewReplacer("\\", "\\\\", "\"", "\\\"") +var keyEscaper = strings.NewReplacer(" ", "\\ ", ",", "\\,", "=", "\\=") + // Parser parses json inputs containing dropwizard metrics, // either top-level or embedded inside a json field. // This parser is using gjon for retrieving paths within the json file. @@ -211,7 +214,7 @@ func (p *Parser) readDWMetrics(metricType string, dwms interface{}, metrics []te measurementWithTags := measurementName for tagName, tagValue := range tags { - tagKeyValue := fmt.Sprintf("%s=%s", tagName, tagValue) + tagKeyValue := fmt.Sprintf("%s=%s", keyEscaper.Replace(tagName), keyEscaper.Replace(tagValue)) measurementWithTags = fmt.Sprintf("%s,%s", measurementWithTags, tagKeyValue) } @@ -219,10 +222,14 @@ func (p *Parser) readDWMetrics(metricType string, dwms interface{}, metrics []te switch t := dwmFields.(type) { case map[string]interface{}: // json object for fieldName, fieldValue := range t { - + key := keyEscaper.Replace(fieldPrefix + fieldName) switch v := fieldValue.(type) { case float64: - fields = append(fields, fmt.Sprintf("%s%s=%f", fieldPrefix, fieldName, v)) + fields = append(fields, fmt.Sprintf("%s=%f", key, v)) + case string: + fields = append(fields, fmt.Sprintf("%s=\"%s\"", key, fieldEscaper.Replace(v))) + case bool: + fields = append(fields, fmt.Sprintf("%s=%t", key, v)) default: // ignore } } diff --git a/plugins/parsers/dropwizard/parser_test.go b/plugins/parsers/dropwizard/parser_test.go index 5834eb516..fa92f3c30 100644 --- a/plugins/parsers/dropwizard/parser_test.go +++ b/plugins/parsers/dropwizard/parser_test.go @@ -67,7 +67,8 @@ const validEmbeddedCounterJSON = ` "time" : "2017-02-22T14:33:03.662+02:00", "tags" : { "tag1" : "green", - "tag2" : "yellow" + "tag2" : "yellow", + "tag3 space,comma=equals" : "red ,=" }, "metrics" : { "counters" : { @@ -99,7 +100,12 @@ func TestParseValidEmbeddedCounterJSON(t *testing.T) { assert.Equal(t, map[string]interface{}{ "count": float64(1), }, metrics[0].Fields()) - assert.Equal(t, map[string]string{"metric_type": "counter", "tag1": "green", "tag2": "yellow"}, metrics[0].Tags()) + assert.Equal(t, map[string]string{ + "metric_type": "counter", + "tag1": "green", + "tag2": "yellow", + "tag3 space,comma=equals": "red ,=", + }, metrics[0].Tags()) assert.True(t, metricTime.Equal(metrics[0].Time()), fmt.Sprintf("%s should be equal to %s", metrics[0].Time(), metricTime)) // now test json tags through TagPathsMap @@ -147,6 +153,7 @@ func TestParseValidMeterJSON1(t *testing.T) { "m1_rate": float64(1), "m5_rate": float64(1), "mean_rate": float64(1), + "units": "events/second", }, metrics[0].Fields()) assert.Equal(t, map[string]string{"metric_type": "meter"}, metrics[0].Tags()) @@ -186,6 +193,7 @@ func TestParseValidMeterJSON2(t *testing.T) { "m1_rate": float64(2), "m5_rate": float64(2), "mean_rate": float64(2), + "units": "events/second", }, metrics[0].Fields()) assert.Equal(t, map[string]string{"metric_type": "meter", "key": "value"}, metrics[0].Tags()) } @@ -198,7 +206,7 @@ const validGaugeJSON = ` "meters" : {}, "gauges" : { "measurement" : { - "value" : 0 + "value" : true } }, "histograms" : {}, @@ -214,7 +222,7 @@ func TestParseValidGaugeJSON(t *testing.T) { assert.Len(t, metrics, 1) assert.Equal(t, "measurement", metrics[0].Name()) assert.Equal(t, map[string]interface{}{ - "value": float64(0), + "value": true, }, metrics[0].Fields()) assert.Equal(t, map[string]string{"metric_type": "gauge"}, metrics[0].Tags()) } @@ -308,21 +316,23 @@ func TestParseValidTimerJSON(t *testing.T) { assert.Len(t, metrics, 1) assert.Equal(t, "measurement", metrics[0].Name()) assert.Equal(t, map[string]interface{}{ - "count": float64(1), - "max": float64(2), - "mean": float64(3), - "min": float64(4), - "p50": float64(5), - "p75": float64(6), - "p95": float64(7), - "p98": float64(8), - "p99": float64(9), - "p999": float64(10), - "stddev": float64(11), - "m15_rate": float64(12), - "m1_rate": float64(13), - "m5_rate": float64(14), - "mean_rate": float64(15), + "count": float64(1), + "max": float64(2), + "mean": float64(3), + "min": float64(4), + "p50": float64(5), + "p75": float64(6), + "p95": float64(7), + "p98": float64(8), + "p99": float64(9), + "p999": float64(10), + "stddev": float64(11), + "m15_rate": float64(12), + "m1_rate": float64(13), + "m5_rate": float64(14), + "mean_rate": float64(15), + "duration_units": "seconds", + "rate_units": "calls/second", }, metrics[0].Fields()) assert.Equal(t, map[string]string{"metric_type": "timer"}, metrics[0].Tags()) }