Skip to content

Commit cd3de5d

Browse files
committed
Fix scraper log parsing regression
The extractLogs method was trying to parse JSON logs using logfmt decoder, which caused 'logfmt syntax error at pos 2 on line 1: unexpected '"'' errors. Modified extractLogs to parse JSON format from zerolog and convert to logfmt format for consistency with existing log processing pipeline.
1 parent 664d9e7 commit cd3de5d

File tree

3 files changed

+67
-40
lines changed

3 files changed

+67
-40
lines changed

go.mod

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ require (
4040
github.com/spf13/afero v1.14.0
4141
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792
4242
gopkg.in/yaml.v3 v3.0.1
43-
kernel.org/pub/linux/libs/security/libcap/cap v1.2.76
4443
)
4544

4645
require (
@@ -73,7 +72,6 @@ require (
7372
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
7473
google.golang.org/protobuf v1.36.6 // indirect
7574
gopkg.in/yaml.v2 v2.4.0 // indirect
76-
kernel.org/pub/linux/libs/security/libcap/psx v1.2.76 // indirect
7775
)
7876

7977
replace github.com/tonobo/mtr => github.com/grafana/mtr v0.1.1-0.20221107202107-a9806fdda166

go.sum

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,3 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
272272
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
273273
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
274274
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
275-
kernel.org/pub/linux/libs/security/libcap/cap v1.2.76 h1:mrdLPj8ujM6eIKGtd1PkkuCIodpFFDM42Cfm0YODkIM=
276-
kernel.org/pub/linux/libs/security/libcap/cap v1.2.76/go.mod h1:7V2BQeHnVAQwhCnCPJ977giCeGDiywVewWF+8vkpPlc=
277-
kernel.org/pub/linux/libs/security/libcap/psx v1.2.76 h1:3DyzQ30OHt3wiOZVL1se2g1PAPJIU7+tMUyvfMUj1dY=
278-
kernel.org/pub/linux/libs/security/libcap/psx v1.2.76/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=

internal/scraper/scraper.go

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package scraper
33
import (
44
"bytes"
55
"context"
6+
"encoding/json"
67
"errors"
78
"fmt"
89
"math"
@@ -757,59 +758,69 @@ func getDerivedMetrics(mfs []*dto.MetricFamily, summaries map[uint64]prometheus.
757758
}
758759

759760
func (s Scraper) extractLogs(t time.Time, logs []byte, sharedLabels []labelPair) Streams {
760-
var line strings.Builder
761-
762-
dec := logfmt.NewDecoder(bytes.NewReader(logs))
763-
764761
labels := make([]labelPair, 0, len(sharedLabels))
765762
var entries []logproto.Entry
766-
RECORD:
767-
for dec.ScanRecord() {
768-
var t time.Time
769763

770-
line.Reset()
764+
// Split logs into lines and process each JSON line
765+
lines := strings.Split(string(logs), "\n")
766+
for _, line := range lines {
767+
line = strings.TrimSpace(line)
768+
if line == "" {
769+
continue
770+
}
771771

772-
enc := logfmt.NewEncoder(&line)
772+
// Parse JSON log entry
773+
var logEntry map[string]interface{}
774+
if err := json.Unmarshal([]byte(line), &logEntry); err != nil {
775+
s.logger.Warn().Err(err).Str("line", line).Msg("invalid JSON log entry")
776+
continue
777+
}
773778

774-
labels = labels[:0]
775-
labels = append(labels, sharedLabels...)
779+
// Extract timestamp
780+
logTime := s.extractLogTimestamp(logEntry, t)
776781

777-
for dec.ScanKeyval() {
778-
value := dec.Value()
782+
// Convert JSON back to logfmt format for consistency
783+
var logfmtLine strings.Builder
784+
enc := logfmt.NewEncoder(&logfmtLine)
779785

780-
switch key := dec.Key(); string(key) {
781-
case "ts":
782-
var err error
783-
t, err = time.Parse(time.RFC3339Nano, string(value))
784-
if err != nil {
785-
// We should never hit this as the timestamp string in the log should be valid.
786-
// Without a timestamp we cannot do anything. And we cannot use something like
787-
// time.Now() because that would mess up other entries
788-
s.logger.Warn().Err(err).Bytes("value", value).Msg("invalid timestamp scanning logs")
789-
continue RECORD
790-
}
786+
for key, value := range logEntry {
787+
// Skip the time field as it's handled separately
788+
if key == "time" {
789+
continue
790+
}
791791

792+
var valueStr string
793+
switch v := value.(type) {
794+
case string:
795+
valueStr = v
796+
case float64:
797+
valueStr = strconv.FormatFloat(v, 'f', -1, 64)
798+
case bool:
799+
valueStr = strconv.FormatBool(v)
800+
case nil:
801+
valueStr = ""
792802
default:
793-
if err := enc.EncodeKeyval(key, value); err != nil {
794-
// We should never hit this because all the entries are valid.
795-
s.logger.Warn().Err(err).Bytes("key", key).Bytes("value", value).Msg("invalid entry scanning logs")
796-
continue RECORD
797-
}
803+
valueStr = fmt.Sprintf("%v", v)
804+
}
805+
806+
if err := enc.EncodeKeyval([]byte(key), []byte(valueStr)); err != nil {
807+
s.logger.Warn().Err(err).Str("key", key).Str("value", valueStr).Msg("invalid logfmt encoding")
808+
continue
798809
}
799810
}
800811

801812
if err := enc.EndRecord(); err != nil {
802813
s.logger.Warn().Err(err).Msg("encoding logs")
803814
}
815+
804816
entries = append(entries, logproto.Entry{
805-
Timestamp: t,
806-
Line: line.String(),
817+
Timestamp: logTime,
818+
Line: logfmtLine.String(),
807819
})
808820
}
809821

810-
if err := dec.Err(); err != nil {
811-
s.logger.Error().Err(err).Msg("decoding logs")
812-
}
822+
// Copy shared labels
823+
labels = append(labels, sharedLabels...)
813824

814825
return Streams{
815826
logproto.Stream{
@@ -819,6 +830,28 @@ RECORD:
819830
}
820831
}
821832

833+
// extractLogTimestamp extracts and parses the timestamp from a log entry
834+
func (s Scraper) extractLogTimestamp(logEntry map[string]interface{}, fallbackTime time.Time) time.Time {
835+
// Try to get timestamp as string first
836+
if tsStr, ok := logEntry["time"].(string); ok {
837+
if ts, err := time.Parse(time.RFC3339Nano, tsStr); err == nil {
838+
return ts
839+
}
840+
// If string parsing fails, try as float64 (Unix timestamp)
841+
if tsFloat, ok := logEntry["time"].(float64); ok {
842+
return time.Unix(int64(tsFloat), 0)
843+
}
844+
}
845+
846+
// Try to get timestamp as float64 directly
847+
if tsFloat, ok := logEntry["time"].(float64); ok {
848+
return time.Unix(int64(tsFloat), 0)
849+
}
850+
851+
// Fallback to provided time
852+
return fallbackTime
853+
}
854+
822855
func (s Scraper) extractTimeseries(t time.Time, metrics []*dto.MetricFamily, sharedLabels []labelPair) TimeSeries {
823856
return extractTimeseries(t, metrics, sharedLabels, s.summaries, s.histograms, s.logger)
824857
}

0 commit comments

Comments
 (0)