mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
model/textparse: improve error outputs
Parsing errors in the Prometheus HTTP format parser are very hard to investigate since they only approximately indicate what is going wrong in the parser and don't provide any information about the incorrect input. As such it is very hard to tell what is wrong in the format exposed by the application. Signed-off-by: Damien Grisonnet <dgrisonn@redhat.com>
This commit is contained in:
parent
c70d85baed
commit
e3b5416d05
|
@ -223,6 +223,10 @@ func (p *OpenMetricsParser) nextToken() token {
|
||||||
return tok
|
return tok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseError(exp string, got token) error {
|
||||||
|
return fmt.Errorf("%s, got %q", exp, got)
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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\"",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue