Merge pull request #11682 from dgrisonnet/parsing-errors

Improve the Prometheus parser error outputs to be more comprehensive
This commit is contained in:
Levi Harrison 2023-02-22 17:35:27 -05:00 committed by GitHub
commit 64ff6bece6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 130 additions and 125 deletions

View file

@ -46,13 +46,6 @@ func (l *openMetricsLexer) buf() []byte {
return l.b[l.start:l.i] return l.b[l.start:l.i]
} }
func (l *openMetricsLexer) cur() byte {
if l.i < len(l.b) {
return l.b[l.i]
}
return byte(' ')
}
// next advances the openMetricsLexer to the next character. // next advances the openMetricsLexer to the next character.
func (l *openMetricsLexer) next() byte { func (l *openMetricsLexer) next() byte {
l.i++ l.i++
@ -223,6 +216,14 @@ func (p *OpenMetricsParser) nextToken() token {
return tok return tok
} }
func (p *OpenMetricsParser) parseError(exp string, got token) error {
e := p.l.i + 1
if len(p.l.b) < e {
e = len(p.l.b)
}
return fmt.Errorf("%s, got %q (%q) while parsing: %q", exp, p.l.b[p.l.start:e], got, p.l.b[p.start:e])
}
// Next advances the parser to the next sample. It returns false if no // Next advances the parser to the next sample. It returns false if no
// more samples were read or an error occurred. // more samples were read or an error occurred.
func (p *OpenMetricsParser) Next() (Entry, error) { func (p *OpenMetricsParser) Next() (Entry, error) {
@ -248,7 +249,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
case tMName: case tMName:
p.offsets = append(p.offsets, p.l.start, p.l.i) p.offsets = append(p.offsets, p.l.start, p.l.i)
default: default:
return EntryInvalid, parseError("expected metric name after "+t.String(), t2) return EntryInvalid, p.parseError("expected metric name after "+t.String(), t2)
} }
switch t2 := p.nextToken(); t2 { switch t2 := p.nextToken(); t2 {
case tText: case tText:
@ -284,7 +285,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
} }
case tHelp: case tHelp:
if !utf8.Valid(p.text) { if !utf8.Valid(p.text) {
return EntryInvalid, errors.New("help text is not a valid utf8 string") return EntryInvalid, fmt.Errorf("help text %q is not a valid utf8 string", p.text)
} }
} }
switch t { switch t {
@ -297,7 +298,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
u := yoloString(p.text) u := yoloString(p.text)
if len(u) > 0 { if len(u) > 0 {
if !strings.HasSuffix(m, u) || len(m) < len(u)+1 || p.l.b[p.offsets[1]-len(u)-1] != '_' { if !strings.HasSuffix(m, u) || len(m) < len(u)+1 || p.l.b[p.offsets[1]-len(u)-1] != '_' {
return EntryInvalid, fmt.Errorf("unit not a suffix of metric %q", m) return EntryInvalid, fmt.Errorf("unit %q not a suffix of metric %q", u, m)
} }
} }
return EntryUnit, nil return EntryUnit, nil
@ -336,10 +337,10 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
var ts float64 var ts float64
// A float is enough to hold what we need for millisecond resolution. // A float is enough to hold what we need for millisecond resolution.
if ts, err = parseFloat(yoloString(p.l.buf()[1:])); err != nil { if ts, err = parseFloat(yoloString(p.l.buf()[1:])); err != nil {
return EntryInvalid, err return EntryInvalid, fmt.Errorf("%v while parsing: %q", err, p.l.b[p.start:p.l.i])
} }
if math.IsNaN(ts) || math.IsInf(ts, 0) { if math.IsNaN(ts) || math.IsInf(ts, 0) {
return EntryInvalid, errors.New("invalid timestamp") return EntryInvalid, fmt.Errorf("invalid timestamp %f", ts)
} }
p.ts = int64(ts * 1000) p.ts = int64(ts * 1000)
switch t3 := p.nextToken(); t3 { switch t3 := p.nextToken(); t3 {
@ -349,15 +350,15 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
return EntryInvalid, err return EntryInvalid, err
} }
default: default:
return EntryInvalid, parseError("expected next entry after timestamp", t3) return EntryInvalid, p.parseError("expected next entry after timestamp", t3)
} }
default: default:
return EntryInvalid, parseError("expected timestamp or # symbol", t2) return EntryInvalid, p.parseError("expected timestamp or # symbol", t2)
} }
return EntrySeries, nil return EntrySeries, nil
default: default:
err = fmt.Errorf("%q %q is not a valid start token", t, string(p.l.cur())) err = p.parseError("expected a valid start token", t)
} }
return EntryInvalid, err return EntryInvalid, err
} }
@ -395,19 +396,19 @@ func (p *OpenMetricsParser) parseComment() error {
var ts float64 var ts float64
// A float is enough to hold what we need for millisecond resolution. // A float is enough to hold what we need for millisecond resolution.
if ts, err = parseFloat(yoloString(p.l.buf()[1:])); err != nil { if ts, err = parseFloat(yoloString(p.l.buf()[1:])); err != nil {
return err return fmt.Errorf("%v while parsing: %q", err, p.l.b[p.start:p.l.i])
} }
if math.IsNaN(ts) || math.IsInf(ts, 0) { if math.IsNaN(ts) || math.IsInf(ts, 0) {
return errors.New("invalid exemplar timestamp") return fmt.Errorf("invalid exemplar timestamp %f", ts)
} }
p.exemplarTs = int64(ts * 1000) p.exemplarTs = int64(ts * 1000)
switch t3 := p.nextToken(); t3 { switch t3 := p.nextToken(); t3 {
case tLinebreak: case tLinebreak:
default: default:
return parseError("expected next entry after exemplar timestamp", t3) return p.parseError("expected next entry after exemplar timestamp", t3)
} }
default: default:
return parseError("expected timestamp or comment", t2) return p.parseError("expected timestamp or comment", t2)
} }
return nil return nil
} }
@ -421,21 +422,21 @@ func (p *OpenMetricsParser) parseLVals(offsets []int) ([]int, error) {
return offsets, nil return offsets, nil
case tComma: case tComma:
if first { if first {
return nil, parseError("expected label name or left brace", t) return nil, p.parseError("expected label name or left brace", t)
} }
t = p.nextToken() t = p.nextToken()
if t != tLName { if t != tLName {
return nil, parseError("expected label name", t) return nil, p.parseError("expected label name", t)
} }
case tLName: case tLName:
if !first { if !first {
return nil, parseError("expected comma", t) return nil, p.parseError("expected comma", t)
} }
default: default:
if first { if first {
return nil, parseError("expected label name or left brace", t) return nil, p.parseError("expected label name or left brace", t)
} }
return nil, parseError("expected comma or left brace", t) return nil, p.parseError("expected comma or left brace", t)
} }
first = false first = false
@ -444,13 +445,13 @@ func (p *OpenMetricsParser) parseLVals(offsets []int) ([]int, error) {
offsets = append(offsets, p.l.start, p.l.i) offsets = append(offsets, p.l.start, p.l.i)
if t := p.nextToken(); t != tEqual { if t := p.nextToken(); t != tEqual {
return nil, parseError("expected equal", t) return nil, p.parseError("expected equal", t)
} }
if t := p.nextToken(); t != tLValue { if t := p.nextToken(); t != tLValue {
return nil, parseError("expected label value", t) return nil, p.parseError("expected label value", t)
} }
if !utf8.Valid(p.l.buf()) { if !utf8.Valid(p.l.buf()) {
return nil, errors.New("invalid UTF-8 label value") return nil, fmt.Errorf("invalid UTF-8 label value: %q", p.l.buf())
} }
// The openMetricsLexer ensures the value string is quoted. Strip first // The openMetricsLexer ensures the value string is quoted. Strip first
@ -461,11 +462,11 @@ func (p *OpenMetricsParser) parseLVals(offsets []int) ([]int, error) {
func (p *OpenMetricsParser) getFloatValue(t token, after string) (float64, error) { func (p *OpenMetricsParser) getFloatValue(t token, after string) (float64, error) {
if t != tValue { if t != tValue {
return 0, parseError(fmt.Sprintf("expected value after %v", after), t) return 0, p.parseError(fmt.Sprintf("expected value after %v", after), t)
} }
val, err := parseFloat(yoloString(p.l.buf()[1:])) val, err := parseFloat(yoloString(p.l.buf()[1:]))
if err != nil { if err != nil {
return 0, err return 0, fmt.Errorf("%v while parsing: %q", err, p.l.b[p.start:p.l.i])
} }
// Ensure canonical NaN value. // Ensure canonical NaN value.
if math.IsNaN(p.exemplarVal) { if math.IsNaN(p.exemplarVal) {

View file

@ -293,11 +293,11 @@ func TestOpenMetricsParseErrors(t *testing.T) {
}, },
{ {
input: "\n", input: "\n",
err: "\"INVALID\" \"\\n\" is not a valid start token", err: "expected a valid start token, got \"\\n\" (\"INVALID\") while parsing: \"\\n\"",
}, },
{ {
input: "metric", input: "metric",
err: "expected value after metric, got \"EOF\"", err: "expected value after metric, got \"metric\" (\"EOF\") while parsing: \"metric\"",
}, },
{ {
input: "metric 1", input: "metric 1",
@ -313,19 +313,19 @@ func TestOpenMetricsParseErrors(t *testing.T) {
}, },
{ {
input: "a\n#EOF\n", input: "a\n#EOF\n",
err: "expected value after metric, got \"INVALID\"", err: "expected value after metric, got \"\\n\" (\"INVALID\") while parsing: \"a\\n\"",
}, },
{ {
input: "\n\n#EOF\n", input: "\n\n#EOF\n",
err: "\"INVALID\" \"\\n\" is not a valid start token", err: "expected a valid start token, got \"\\n\" (\"INVALID\") while parsing: \"\\n\"",
}, },
{ {
input: " a 1\n#EOF\n", input: " a 1\n#EOF\n",
err: "\"INVALID\" \" \" is not a valid start token", err: "expected a valid start token, got \" \" (\"INVALID\") while parsing: \" \"",
}, },
{ {
input: "9\n#EOF\n", input: "9\n#EOF\n",
err: "\"INVALID\" \"9\" is not a valid start token", err: "expected a valid start token, got \"9\" (\"INVALID\") while parsing: \"9\"",
}, },
{ {
input: "# TYPE u untyped\n#EOF\n", input: "# TYPE u untyped\n#EOF\n",
@ -337,11 +337,11 @@ func TestOpenMetricsParseErrors(t *testing.T) {
}, },
{ {
input: "# TYPE c counter\n#EOF\n", input: "# TYPE c counter\n#EOF\n",
err: "\"INVALID\" \" \" is not a valid start token", err: "expected a valid start token, got \"# \" (\"INVALID\") while parsing: \"# \"",
}, },
{ {
input: "# TYPE \n#EOF\n", input: "# TYPE \n#EOF\n",
err: "expected metric name after TYPE, got \"INVALID\"", err: "expected metric name after TYPE, got \"\\n\" (\"INVALID\") while parsing: \"# TYPE \\n\"",
}, },
{ {
input: "# TYPE m\n#EOF\n", input: "# TYPE m\n#EOF\n",
@ -349,19 +349,19 @@ func TestOpenMetricsParseErrors(t *testing.T) {
}, },
{ {
input: "# UNIT metric suffix\n#EOF\n", input: "# UNIT metric suffix\n#EOF\n",
err: "unit not a suffix of metric \"metric\"", err: "unit \"suffix\" not a suffix of metric \"metric\"",
}, },
{ {
input: "# UNIT metricsuffix suffix\n#EOF\n", input: "# UNIT metricsuffix suffix\n#EOF\n",
err: "unit not a suffix of metric \"metricsuffix\"", err: "unit \"suffix\" not a suffix of metric \"metricsuffix\"",
}, },
{ {
input: "# UNIT m suffix\n#EOF\n", input: "# UNIT m suffix\n#EOF\n",
err: "unit not a suffix of metric \"m\"", err: "unit \"suffix\" not a suffix of metric \"m\"",
}, },
{ {
input: "# UNIT \n#EOF\n", input: "# UNIT \n#EOF\n",
err: "expected metric name after UNIT, got \"INVALID\"", err: "expected metric name after UNIT, got \"\\n\" (\"INVALID\") while parsing: \"# UNIT \\n\"",
}, },
{ {
input: "# UNIT m\n#EOF\n", input: "# UNIT m\n#EOF\n",
@ -369,7 +369,7 @@ func TestOpenMetricsParseErrors(t *testing.T) {
}, },
{ {
input: "# HELP \n#EOF\n", input: "# HELP \n#EOF\n",
err: "expected metric name after HELP, got \"INVALID\"", err: "expected metric name after HELP, got \"\\n\" (\"INVALID\") while parsing: \"# HELP \\n\"",
}, },
{ {
input: "# HELP m\n#EOF\n", input: "# HELP m\n#EOF\n",
@ -377,27 +377,27 @@ func TestOpenMetricsParseErrors(t *testing.T) {
}, },
{ {
input: "a\t1\n#EOF\n", input: "a\t1\n#EOF\n",
err: "expected value after metric, got \"INVALID\"", err: "expected value after metric, got \"\\t\" (\"INVALID\") while parsing: \"a\\t\"",
}, },
{ {
input: "a 1\t2\n#EOF\n", input: "a 1\t2\n#EOF\n",
err: "strconv.ParseFloat: parsing \"1\\t2\": invalid syntax", err: "strconv.ParseFloat: parsing \"1\\t2\": invalid syntax while parsing: \"a 1\\t2\"",
}, },
{ {
input: "a 1 2 \n#EOF\n", input: "a 1 2 \n#EOF\n",
err: "expected next entry after timestamp, got \"INVALID\"", err: "expected next entry after timestamp, got \" \\n\" (\"INVALID\") while parsing: \"a 1 2 \\n\"",
}, },
{ {
input: "a 1 2 #\n#EOF\n", input: "a 1 2 #\n#EOF\n",
err: "expected next entry after timestamp, got \"TIMESTAMP\"", err: "expected next entry after timestamp, got \" #\\n\" (\"TIMESTAMP\") while parsing: \"a 1 2 #\\n\"",
}, },
{ {
input: "a 1 1z\n#EOF\n", input: "a 1 1z\n#EOF\n",
err: "strconv.ParseFloat: parsing \"1z\": invalid syntax", err: "strconv.ParseFloat: parsing \"1z\": invalid syntax while parsing: \"a 1 1z\"",
}, },
{ {
input: " # EOF\n", input: " # EOF\n",
err: "\"INVALID\" \" \" is not a valid start token", err: "expected a valid start token, got \" \" (\"INVALID\") while parsing: \" \"",
}, },
{ {
input: "# EOF\na 1", input: "# EOF\na 1",
@ -413,7 +413,7 @@ func TestOpenMetricsParseErrors(t *testing.T) {
}, },
{ {
input: "#\tTYPE c counter\n", input: "#\tTYPE c counter\n",
err: "\"INVALID\" \"\\t\" is not a valid start token", err: "expected a valid start token, got \"#\\t\" (\"INVALID\") while parsing: \"#\\t\"",
}, },
{ {
input: "# TYPE c counter\n", input: "# TYPE c counter\n",
@ -421,79 +421,79 @@ func TestOpenMetricsParseErrors(t *testing.T) {
}, },
{ {
input: "a 1 1 1\n# EOF\n", input: "a 1 1 1\n# EOF\n",
err: "expected next entry after timestamp, got \"TIMESTAMP\"", err: "expected next entry after timestamp, got \" 1\\n\" (\"TIMESTAMP\") while parsing: \"a 1 1 1\\n\"",
}, },
{ {
input: "a{b='c'} 1\n# EOF\n", input: "a{b='c'} 1\n# EOF\n",
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"'\" (\"INVALID\") while parsing: \"a{b='\"",
}, },
{ {
input: "a{b=\"c\",} 1\n# EOF\n", input: "a{b=\"c\",} 1\n# EOF\n",
err: "expected label name, got \"BCLOSE\"", err: "expected label name, got \"} \" (\"BCLOSE\") while parsing: \"a{b=\\\"c\\\",} \"",
}, },
{ {
input: "a{,b=\"c\"} 1\n# EOF\n", input: "a{,b=\"c\"} 1\n# EOF\n",
err: "expected label name or left brace, got \"COMMA\"", err: "expected label name or left brace, got \",b\" (\"COMMA\") while parsing: \"a{,b\"",
}, },
{ {
input: "a{b=\"c\"d=\"e\"} 1\n# EOF\n", input: "a{b=\"c\"d=\"e\"} 1\n# EOF\n",
err: "expected comma, got \"LNAME\"", err: "expected comma, got \"d=\" (\"LNAME\") while parsing: \"a{b=\\\"c\\\"d=\"",
}, },
{ {
input: "a{b=\"c\",,d=\"e\"} 1\n# EOF\n", input: "a{b=\"c\",,d=\"e\"} 1\n# EOF\n",
err: "expected label name, got \"COMMA\"", err: "expected label name, got \",d\" (\"COMMA\") while parsing: \"a{b=\\\"c\\\",,d\"",
}, },
{ {
input: "a{b=\n# EOF\n", input: "a{b=\n# EOF\n",
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"\\n\" (\"INVALID\") while parsing: \"a{b=\\n\"",
}, },
{ {
input: "a{\xff=\"foo\"} 1\n# EOF\n", input: "a{\xff=\"foo\"} 1\n# EOF\n",
err: "expected label name or left brace, got \"INVALID\"", err: "expected label name or left brace, got \"\\xff\" (\"INVALID\") while parsing: \"a{\\xff\"",
}, },
{ {
input: "a{b=\"\xff\"} 1\n# EOF\n", input: "a{b=\"\xff\"} 1\n# EOF\n",
err: "invalid UTF-8 label value", err: "invalid UTF-8 label value: \"\\\"\\xff\\\"\"",
}, },
{ {
input: "a true\n", input: "a true\n",
err: "strconv.ParseFloat: parsing \"true\": invalid syntax", err: "strconv.ParseFloat: parsing \"true\": invalid syntax while parsing: \"a true\"",
}, },
{ {
input: "something_weird{problem=\"\n# EOF\n", input: "something_weird{problem=\"\n# EOF\n",
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"\\\"\\n\" (\"INVALID\") while parsing: \"something_weird{problem=\\\"\\n\"",
}, },
{ {
input: "empty_label_name{=\"\"} 0\n# EOF\n", input: "empty_label_name{=\"\"} 0\n# EOF\n",
err: "expected label name or left brace, got \"EQUAL\"", err: "expected label name or left brace, got \"=\\\"\" (\"EQUAL\") while parsing: \"empty_label_name{=\\\"\"",
}, },
{ {
input: "foo 1_2\n\n# EOF\n", input: "foo 1_2\n\n# EOF\n",
err: "unsupported character in float", err: "unsupported character in float while parsing: \"foo 1_2\"",
}, },
{ {
input: "foo 0x1p-3\n\n# EOF\n", input: "foo 0x1p-3\n\n# EOF\n",
err: "unsupported character in float", err: "unsupported character in float while parsing: \"foo 0x1p-3\"",
}, },
{ {
input: "foo 0x1P-3\n\n# EOF\n", input: "foo 0x1P-3\n\n# EOF\n",
err: "unsupported character in float", err: "unsupported character in float while parsing: \"foo 0x1P-3\"",
}, },
{ {
input: "foo 0 1_2\n\n# EOF\n", input: "foo 0 1_2\n\n# EOF\n",
err: "unsupported character in float", err: "unsupported character in float while parsing: \"foo 0 1_2\"",
}, },
{ {
input: "custom_metric_total 1 # {aa=bb}\n# EOF\n", input: "custom_metric_total 1 # {aa=bb}\n# EOF\n",
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"b\" (\"INVALID\") while parsing: \"custom_metric_total 1 # {aa=b\"",
}, },
{ {
input: "custom_metric_total 1 # {aa=\"bb\"}\n# EOF\n", input: "custom_metric_total 1 # {aa=\"bb\"}\n# EOF\n",
err: "expected value after exemplar labels, got \"INVALID\"", err: "expected value after exemplar labels, got \"\\n\" (\"INVALID\") while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\"}\\n\"",
}, },
{ {
input: `custom_metric_total 1 # {aa="bb"}`, input: `custom_metric_total 1 # {aa="bb"}`,
err: "expected value after exemplar labels, got \"EOF\"", err: "expected value after exemplar labels, got \"}\" (\"EOF\") while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\"}\"",
}, },
{ {
input: `custom_metric 1 # {aa="bb"}`, input: `custom_metric 1 # {aa="bb"}`,
@ -501,55 +501,55 @@ func TestOpenMetricsParseErrors(t *testing.T) {
}, },
{ {
input: `custom_metric_total 1 # {aa="bb",,cc="dd"} 1`, input: `custom_metric_total 1 # {aa="bb",,cc="dd"} 1`,
err: "expected label name, got \"COMMA\"", err: "expected label name, got \",c\" (\"COMMA\") while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\",,c\"",
}, },
{ {
input: `custom_metric_total 1 # {aa="bb"} 1_2`, input: `custom_metric_total 1 # {aa="bb"} 1_2`,
err: "unsupported character in float", err: "unsupported character in float while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\"} 1_2\"",
}, },
{ {
input: `custom_metric_total 1 # {aa="bb"} 0x1p-3`, input: `custom_metric_total 1 # {aa="bb"} 0x1p-3`,
err: "unsupported character in float", err: "unsupported character in float while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\"} 0x1p-3\"",
}, },
{ {
input: `custom_metric_total 1 # {aa="bb"} true`, input: `custom_metric_total 1 # {aa="bb"} true`,
err: "strconv.ParseFloat: parsing \"true\": invalid syntax", err: "strconv.ParseFloat: parsing \"true\": invalid syntax while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\"} true\"",
}, },
{ {
input: `custom_metric_total 1 # {aa="bb",cc=}`, input: `custom_metric_total 1 # {aa="bb",cc=}`,
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"}\" (\"INVALID\") while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\",cc=}\"",
}, },
{ {
input: `custom_metric_total 1 # {aa=\"\xff\"} 9.0`, input: `custom_metric_total 1 # {aa=\"\xff\"} 9.0`,
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"\\\\\" (\"INVALID\") while parsing: \"custom_metric_total 1 # {aa=\\\\\"",
}, },
{ {
input: `{b="c",} 1`, input: `{b="c",} 1`,
err: `"INVALID" "{" is not a valid start token`, err: "expected a valid start token, got \"{\" (\"INVALID\") while parsing: \"{\"",
}, },
{ {
input: `a 1 NaN`, input: `a 1 NaN`,
err: `invalid timestamp`, err: `invalid timestamp NaN`,
}, },
{ {
input: `a 1 -Inf`, input: `a 1 -Inf`,
err: `invalid timestamp`, err: `invalid timestamp -Inf`,
}, },
{ {
input: `a 1 Inf`, input: `a 1 Inf`,
err: `invalid timestamp`, err: `invalid timestamp +Inf`,
}, },
{ {
input: "# TYPE hhh histogram\nhhh_bucket{le=\"+Inf\"} 1 # {aa=\"bb\"} 4 NaN", input: "# TYPE hhh histogram\nhhh_bucket{le=\"+Inf\"} 1 # {aa=\"bb\"} 4 NaN",
err: `invalid exemplar timestamp`, err: `invalid exemplar timestamp NaN`,
}, },
{ {
input: "# TYPE hhh histogram\nhhh_bucket{le=\"+Inf\"} 1 # {aa=\"bb\"} 4 -Inf", input: "# TYPE hhh histogram\nhhh_bucket{le=\"+Inf\"} 1 # {aa=\"bb\"} 4 -Inf",
err: `invalid exemplar timestamp`, err: `invalid exemplar timestamp -Inf`,
}, },
{ {
input: "# TYPE hhh histogram\nhhh_bucket{le=\"+Inf\"} 1 # {aa=\"bb\"} 4 Inf", input: "# TYPE hhh histogram\nhhh_bucket{le=\"+Inf\"} 1 # {aa=\"bb\"} 4 Inf",
err: `invalid exemplar timestamp`, err: `invalid exemplar timestamp +Inf`,
}, },
} }
@ -586,35 +586,35 @@ func TestOMNullByteHandling(t *testing.T) {
}, },
{ {
input: "a{b=\x00\"ssss\"} 1\n# EOF\n", input: "a{b=\x00\"ssss\"} 1\n# EOF\n",
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"\\x00\" (\"INVALID\") while parsing: \"a{b=\\x00\"",
}, },
{ {
input: "a{b=\"\x00", input: "a{b=\"\x00",
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"\\\"\\x00\" (\"INVALID\") while parsing: \"a{b=\\\"\\x00\"",
}, },
{ {
input: "a{b\x00=\"hiih\"} 1", input: "a{b\x00=\"hiih\"} 1",
err: "expected equal, got \"INVALID\"", err: "expected equal, got \"\\x00\" (\"INVALID\") while parsing: \"a{b\\x00\"",
}, },
{ {
input: "a\x00{b=\"ddd\"} 1", input: "a\x00{b=\"ddd\"} 1",
err: "expected value after metric, got \"INVALID\"", err: "expected value after metric, got \"\\x00\" (\"INVALID\") while parsing: \"a\\x00\"",
}, },
{ {
input: "#", input: "#",
err: "\"INVALID\" \" \" is not a valid start token", err: "expected a valid start token, got \"#\" (\"INVALID\") while parsing: \"#\"",
}, },
{ {
input: "# H", input: "# H",
err: "\"INVALID\" \" \" is not a valid start token", err: "expected a valid start token, got \"# H\" (\"INVALID\") while parsing: \"# H\"",
}, },
{ {
input: "custom_metric_total 1 # {b=\x00\"ssss\"} 1\n", input: "custom_metric_total 1 # {b=\x00\"ssss\"} 1\n",
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"\\x00\" (\"INVALID\") while parsing: \"custom_metric_total 1 # {b=\\x00\"",
}, },
{ {
input: "custom_metric_total 1 # {b=\"\x00ss\"} 1\n", input: "custom_metric_total 1 # {b=\"\x00ss\"} 1\n",
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"\\\"\\x00\" (\"INVALID\") while parsing: \"custom_metric_total 1 # {b=\\\"\\x00\"",
}, },
} }

View file

@ -254,8 +254,12 @@ func (p *PromParser) nextToken() token {
} }
} }
func parseError(exp string, got token) error { func (p *PromParser) parseError(exp string, got token) error {
return fmt.Errorf("%s, got %q", exp, got) e := p.l.i + 1
if len(p.l.b) < e {
e = len(p.l.b)
}
return fmt.Errorf("%s, got %q (%q) while parsing: %q", exp, p.l.b[p.l.start:e], got, p.l.b[p.start:e])
} }
// Next advances the parser to the next sample. It returns false if no // Next advances the parser to the next sample. It returns false if no
@ -278,7 +282,7 @@ func (p *PromParser) Next() (Entry, error) {
case tMName: case tMName:
p.offsets = append(p.offsets, p.l.start, p.l.i) p.offsets = append(p.offsets, p.l.start, p.l.i)
default: default:
return EntryInvalid, parseError("expected metric name after "+t.String(), t2) return EntryInvalid, p.parseError("expected metric name after "+t.String(), t2)
} }
switch t2 := p.nextToken(); t2 { switch t2 := p.nextToken(); t2 {
case tText: case tText:
@ -308,11 +312,11 @@ func (p *PromParser) Next() (Entry, error) {
} }
case tHelp: case tHelp:
if !utf8.Valid(p.text) { if !utf8.Valid(p.text) {
return EntryInvalid, fmt.Errorf("help text is not a valid utf8 string") return EntryInvalid, fmt.Errorf("help text %q is not a valid utf8 string", p.text)
} }
} }
if t := p.nextToken(); t != tLinebreak { if t := p.nextToken(); t != tLinebreak {
return EntryInvalid, parseError("linebreak expected after metadata", t) return EntryInvalid, p.parseError("linebreak expected after metadata", t)
} }
switch t { switch t {
case tHelp: case tHelp:
@ -323,7 +327,7 @@ func (p *PromParser) Next() (Entry, error) {
case tComment: case tComment:
p.text = p.l.buf() p.text = p.l.buf()
if t := p.nextToken(); t != tLinebreak { if t := p.nextToken(); t != tLinebreak {
return EntryInvalid, parseError("linebreak expected after comment", t) return EntryInvalid, p.parseError("linebreak expected after comment", t)
} }
return EntryComment, nil return EntryComment, nil
@ -340,10 +344,10 @@ func (p *PromParser) Next() (Entry, error) {
t2 = p.nextToken() t2 = p.nextToken()
} }
if t2 != tValue { if t2 != tValue {
return EntryInvalid, parseError("expected value after metric", t2) return EntryInvalid, p.parseError("expected value after metric", t2)
} }
if p.val, err = parseFloat(yoloString(p.l.buf())); err != nil { if p.val, err = parseFloat(yoloString(p.l.buf())); err != nil {
return EntryInvalid, err return EntryInvalid, fmt.Errorf("%v while parsing: %q", err, p.l.b[p.start:p.l.i])
} }
// Ensure canonical NaN value. // Ensure canonical NaN value.
if math.IsNaN(p.val) { if math.IsNaN(p.val) {
@ -356,18 +360,18 @@ func (p *PromParser) Next() (Entry, error) {
case tTimestamp: case tTimestamp:
p.hasTS = true p.hasTS = true
if p.ts, err = strconv.ParseInt(yoloString(p.l.buf()), 10, 64); err != nil { if p.ts, err = strconv.ParseInt(yoloString(p.l.buf()), 10, 64); err != nil {
return EntryInvalid, err return EntryInvalid, fmt.Errorf("%v while parsing: %q", err, p.l.b[p.start:p.l.i])
} }
if t2 := p.nextToken(); t2 != tLinebreak { if t2 := p.nextToken(); t2 != tLinebreak {
return EntryInvalid, parseError("expected next entry after timestamp", t2) return EntryInvalid, p.parseError("expected next entry after timestamp", t2)
} }
default: default:
return EntryInvalid, parseError("expected timestamp or new record", t) return EntryInvalid, p.parseError("expected timestamp or new record", t)
} }
return EntrySeries, nil return EntrySeries, nil
default: default:
err = fmt.Errorf("%q is not a valid start token", t) err = p.parseError("expected a valid start token", t)
} }
return EntryInvalid, err return EntryInvalid, err
} }
@ -380,18 +384,18 @@ func (p *PromParser) parseLVals() error {
return nil return nil
case tLName: case tLName:
default: default:
return parseError("expected label name", t) return p.parseError("expected label name", t)
} }
p.offsets = append(p.offsets, p.l.start, p.l.i) p.offsets = append(p.offsets, p.l.start, p.l.i)
if t := p.nextToken(); t != tEqual { if t := p.nextToken(); t != tEqual {
return parseError("expected equal", t) return p.parseError("expected equal", t)
} }
if t := p.nextToken(); t != tLValue { if t := p.nextToken(); t != tLValue {
return parseError("expected label value", t) return p.parseError("expected label value", t)
} }
if !utf8.Valid(p.l.buf()) { if !utf8.Valid(p.l.buf()) {
return fmt.Errorf("invalid UTF-8 label value") return fmt.Errorf("invalid UTF-8 label value: %q", p.l.buf())
} }
// The promlexer ensures the value string is quoted. Strip first // The promlexer ensures the value string is quoted. Strip first

View file

@ -219,63 +219,63 @@ func TestPromParseErrors(t *testing.T) {
}{ }{
{ {
input: "a", input: "a",
err: "expected value after metric, got \"INVALID\"", err: "expected value after metric, got \"\\n\" (\"INVALID\") while parsing: \"a\\n\"",
}, },
{ {
input: "a{b='c'} 1\n", input: "a{b='c'} 1\n",
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"'\" (\"INVALID\") while parsing: \"a{b='\"",
}, },
{ {
input: "a{b=\n", input: "a{b=\n",
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"\\n\" (\"INVALID\") while parsing: \"a{b=\\n\"",
}, },
{ {
input: "a{\xff=\"foo\"} 1\n", input: "a{\xff=\"foo\"} 1\n",
err: "expected label name, got \"INVALID\"", err: "expected label name, got \"\\xff\" (\"INVALID\") while parsing: \"a{\\xff\"",
}, },
{ {
input: "a{b=\"\xff\"} 1\n", input: "a{b=\"\xff\"} 1\n",
err: "invalid UTF-8 label value", err: "invalid UTF-8 label value: \"\\\"\\xff\\\"\"",
}, },
{ {
input: "a true\n", input: "a true\n",
err: "strconv.ParseFloat: parsing \"true\": invalid syntax", err: "strconv.ParseFloat: parsing \"true\": invalid syntax while parsing: \"a true\"",
}, },
{ {
input: "something_weird{problem=\"", input: "something_weird{problem=\"",
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"\\\"\\n\" (\"INVALID\") while parsing: \"something_weird{problem=\\\"\\n\"",
}, },
{ {
input: "empty_label_name{=\"\"} 0", input: "empty_label_name{=\"\"} 0",
err: "expected label name, got \"EQUAL\"", err: "expected label name, got \"=\\\"\" (\"EQUAL\") while parsing: \"empty_label_name{=\\\"\"",
}, },
{ {
input: "foo 1_2\n", input: "foo 1_2\n",
err: "unsupported character in float", err: "unsupported character in float while parsing: \"foo 1_2\"",
}, },
{ {
input: "foo 0x1p-3\n", input: "foo 0x1p-3\n",
err: "unsupported character in float", err: "unsupported character in float while parsing: \"foo 0x1p-3\"",
}, },
{ {
input: "foo 0x1P-3\n", input: "foo 0x1P-3\n",
err: "unsupported character in float", err: "unsupported character in float while parsing: \"foo 0x1P-3\"",
}, },
{ {
input: "foo 0 1_2\n", input: "foo 0 1_2\n",
err: "expected next entry after timestamp, got \"INVALID\"", err: "expected next entry after timestamp, got \"_\" (\"INVALID\") while parsing: \"foo 0 1_\"",
}, },
{ {
input: `{a="ok"} 1`, input: `{a="ok"} 1`,
err: `"INVALID" is not a valid start token`, err: "expected a valid start token, got \"{\" (\"INVALID\") while parsing: \"{\"",
}, },
{ {
input: "# TYPE #\n#EOF\n", input: "# TYPE #\n#EOF\n",
err: "expected metric name after TYPE, got \"INVALID\"", err: "expected metric name after TYPE, got \"#\" (\"INVALID\") while parsing: \"# TYPE #\"",
}, },
{ {
input: "# HELP #\n#EOF\n", input: "# HELP #\n#EOF\n",
err: "expected metric name after HELP, got \"INVALID\"", err: "expected metric name after HELP, got \"#\" (\"INVALID\") while parsing: \"# HELP #\"",
}, },
} }
@ -313,23 +313,23 @@ func TestPromNullByteHandling(t *testing.T) {
}, },
{ {
input: "a{b=\x00\"ssss\"} 1\n", input: "a{b=\x00\"ssss\"} 1\n",
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"\\x00\" (\"INVALID\") while parsing: \"a{b=\\x00\"",
}, },
{ {
input: "a{b=\"\x00", input: "a{b=\"\x00",
err: "expected label value, got \"INVALID\"", err: "expected label value, got \"\\\"\\x00\\n\" (\"INVALID\") while parsing: \"a{b=\\\"\\x00\\n\"",
}, },
{ {
input: "a{b\x00=\"hiih\"} 1", input: "a{b\x00=\"hiih\"} 1",
err: "expected equal, got \"INVALID\"", err: "expected equal, got \"\\x00\" (\"INVALID\") while parsing: \"a{b\\x00\"",
}, },
{ {
input: "a\x00{b=\"ddd\"} 1", input: "a\x00{b=\"ddd\"} 1",
err: "expected value after metric, got \"INVALID\"", err: "expected value after metric, got \"\\x00\" (\"INVALID\") while parsing: \"a\\x00\"",
}, },
{ {
input: "a 0 1\x00", input: "a 0 1\x00",
err: "expected next entry after timestamp, got \"INVALID\"", err: "expected next entry after timestamp, got \"\\x00\" (\"INVALID\") while parsing: \"a 0 1\\x00\"",
}, },
} }