diff --git a/internal/models/makemetric.go b/internal/models/makemetric.go index b2c150546..19d10f589 100644 --- a/internal/models/makemetric.go +++ b/internal/models/makemetric.go @@ -150,12 +150,6 @@ func makemetric( continue } case string: - if strings.HasSuffix(val, `\`) { - log.Printf("D! Measurement [%s] field [%s] has a value "+ - "ending with a backslash, skipping", measurement, k) - delete(fields, k) - continue - } fields[k] = v default: fields[k] = v diff --git a/internal/models/running_input_test.go b/internal/models/running_input_test.go index 1a6491e35..c653090e8 100644 --- a/internal/models/running_input_test.go +++ b/internal/models/running_input_test.go @@ -370,16 +370,17 @@ func TestMakeMetric_TrailingSlash(t *testing.T) { expectedTags: map[string]string{}, }, { - name: "Field value with trailing slash dropped", + name: "Field value with trailing slash okay", measurement: `cpu`, fields: map[string]interface{}{ "value": int64(42), - "bad": `xyzzy\`, + "ok": `xyzzy\`, }, tags: map[string]string{}, expectedMeasurement: `cpu`, expectedFields: map[string]interface{}{ "value": int64(42), + "ok": `xyzzy\`, }, expectedTags: map[string]string{}, }, @@ -387,7 +388,7 @@ func TestMakeMetric_TrailingSlash(t *testing.T) { name: "Must have one field after dropped", measurement: `cpu`, fields: map[string]interface{}{ - "bad": `xyzzy\`, + "bad": math.NaN(), }, tags: map[string]string{}, expectedNil: true, diff --git a/metric/metric.go b/metric/metric.go index 63e609290..e46cfd189 100644 --- a/metric/metric.go +++ b/metric/metric.go @@ -21,14 +21,14 @@ func New( t time.Time, mType ...telegraf.ValueType, ) (telegraf.Metric, error) { - if len(fields) == 0 { - return nil, fmt.Errorf("Metric cannot be made without any fields") - } if len(name) == 0 { - return nil, fmt.Errorf("Metric cannot be made with an empty name") + return nil, fmt.Errorf("missing measurement name") + } + if len(fields) == 0 { + return nil, fmt.Errorf("%s: must have one or more fields", name) } if strings.HasSuffix(name, `\`) { - return nil, fmt.Errorf("Metric cannot have measurement name ending with a backslash") + return nil, fmt.Errorf("%s: measurement name cannot end with a backslash", name) } var thisType telegraf.ValueType @@ -49,10 +49,10 @@ func New( taglen := 0 for k, v := range tags { if strings.HasSuffix(k, `\`) { - return nil, fmt.Errorf("Metric cannot have tag key ending with a backslash") + return nil, fmt.Errorf("%s: tag key cannot end with a backslash: %s", name, k) } if strings.HasSuffix(v, `\`) { - return nil, fmt.Errorf("Metric cannot have tag value ending with a backslash") + return nil, fmt.Errorf("%s: tag value cannot end with a backslash: %s", name, v) } if len(k) == 0 || len(v) == 0 { @@ -79,7 +79,7 @@ func New( fieldlen := 0 for k, _ := range fields { if strings.HasSuffix(k, `\`) { - return nil, fmt.Errorf("Metric cannot have field key ending with a backslash") + return nil, fmt.Errorf("%s: field key cannot end with a backslash: %s", name, k) } // 10 bytes is completely arbitrary, but will at least prevent some @@ -102,7 +102,8 @@ func New( } // indexUnescapedByte finds the index of the first byte equal to b in buf that -// is not escaped. Returns -1 if not found. +// is not escaped. Does not allow the escape char to be escaped. Returns -1 if +// not found. func indexUnescapedByte(buf []byte, b byte) int { var keyi int for { @@ -122,6 +123,46 @@ func indexUnescapedByte(buf []byte, b byte) int { return keyi } +// indexUnescapedByteBackslashEscaping finds the index of the first byte equal +// to b in buf that is not escaped. Allows for the escape char `\` to be +// escaped. Returns -1 if not found. +func indexUnescapedByteBackslashEscaping(buf []byte, b byte) int { + var keyi int + for { + i := bytes.IndexByte(buf[keyi:], b) + if i == -1 { + return -1 + } else if i == 0 { + break + } + keyi += i + if countBackslashes(buf, keyi-1)%2 == 0 { + break + } else { + keyi++ + } + } + return keyi +} + +// countBackslashes counts the number of preceding backslashes starting at +// the 'start' index. +func countBackslashes(buf []byte, index int) int { + var count int + for { + if index < 0 { + return count + } + if buf[index] == '\\' { + count++ + index-- + } else { + break + } + } + return count +} + type metric struct { name []byte tags []byte @@ -283,7 +324,7 @@ func (m *metric) Fields() map[string]interface{} { // end index of field value var i3 int if m.fields[i:][i2] == '"' { - i3 = indexUnescapedByte(m.fields[i:][i2+1:], '"') + i3 = indexUnescapedByteBackslashEscaping(m.fields[i:][i2+1:], '"') if i3 == -1 { i3 = len(m.fields[i:]) } diff --git a/metric/metric_test.go b/metric/metric_test.go index b1a23e6df..db71bf92a 100644 --- a/metric/metric_test.go +++ b/metric/metric_test.go @@ -258,6 +258,7 @@ func TestNewMetric_Fields(t *testing.T) { "quote_string": `x"y`, "backslash_quote_string": `x\"y`, "backslash": `x\y`, + "ends_with_backslash": `x\`, } m, err := New("cpu", tags, fields, now) assert.NoError(t, err)