package parser import ( "encoding/json" "maps" "time" ) var ( timeKeys = []string{"time", "timestamp", "ts", "@timestamp"} levelKeys = []string{"level", "lvl", "severity"} msgKeys = []string{"msg", "message"} traceKeys = []string{"trace_id", "traceID", "traceId", "trace.id", "traceparent"} spanKeys = []string{"span_id", "spanID", "spanId", "span.id"} ) type JSONParser struct{} func (JSONParser) Parse(raw string) (LogEntry, bool) { var m map[string]any if err := json.Unmarshal([]byte(raw), &m); err != nil { return LogEntry{}, false } if m == nil { return LogEntry{}, false } entry := LogEntry{Raw: raw, Fields: make(map[string]any)} entry.Timestamp = extractTime(m) entry.Level = extractString(m, levelKeys) entry.Message = extractString(m, msgKeys) entry.TraceID = normalizeTraceID(extractString(m, traceKeys)) entry.SpanID = extractString(m, spanKeys) maps.Copy(entry.Fields, m) return entry, true } func extractString(m map[string]any, keys []string) string { for _, k := range keys { if v, ok := m[k]; ok { delete(m, k) if s, ok := v.(string); ok { return s } return "" } } return "" } func extractTime(m map[string]any) time.Time { for _, k := range timeKeys { v, ok := m[k] if !ok { continue } delete(m, k) return parseTimeValue(v) } return time.Now() } func parseTimeValue(v any) time.Time { switch t := v.(type) { case string: return parseTimeString(t) case float64: return parseTimeNumber(t) } return time.Now() } func parseTimeString(s string) time.Time { for _, layout := range []string{time.RFC3339Nano, time.RFC3339, "2006-01-02T15:04:05.000Z"} { if t, err := time.Parse(layout, s); err == nil { return t } } return time.Now() } func parseTimeNumber(n float64) time.Time { const ( millisThreshold = 1e12 microsThreshold = 1e15 nanosThreshold = 1e18 nanosPerSecond = int64(time.Second) ) switch { case n >= nanosThreshold: return time.Unix(0, int64(n)) case n >= microsThreshold: return time.Unix(0, int64(n)*int64(time.Microsecond)) case n >= millisThreshold: return time.Unix(0, int64(n)*int64(time.Millisecond)) default: secs := int64(n) nanos := int64((n - float64(secs)) * float64(nanosPerSecond)) return time.Unix(secs, nanos) } } func normalizeTraceID(s string) string { const ( traceparentLen = 55 traceparentPrefixLen = 3 traceparentTraceIDLen = 32 dashAfterTrace = 35 dashBeforeFlags = 52 ) if len(s) == traceparentLen && s[2] == '-' && s[dashAfterTrace] == '-' && s[dashBeforeFlags] == '-' { return s[traceparentPrefixLen : traceparentPrefixLen+traceparentTraceIDLen] } return s }