2024-05-20 04:08:30 +02:00
|
|
|
package logstorage
|
2023-06-21 10:10:45 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
|
|
|
"github.com/valyala/fastjson"
|
|
|
|
)
|
|
|
|
|
2024-05-20 04:08:30 +02:00
|
|
|
// JSONParser parses a single JSON log message into Fields.
|
2023-06-21 10:10:45 +02:00
|
|
|
//
|
2024-05-25 00:30:58 +02:00
|
|
|
// See https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model
|
2023-06-21 10:10:45 +02:00
|
|
|
//
|
|
|
|
// Use GetParser() for obtaining the parser.
|
2024-05-20 04:08:30 +02:00
|
|
|
type JSONParser struct {
|
2023-06-21 10:10:45 +02:00
|
|
|
// Fields contains the parsed JSON line after Parse() call
|
|
|
|
//
|
|
|
|
// The Fields are valid until the next call to ParseLogMessage()
|
|
|
|
// or until the parser is returned to the pool with PutParser() call.
|
2024-05-20 04:08:30 +02:00
|
|
|
Fields []Field
|
2023-06-21 10:10:45 +02:00
|
|
|
|
|
|
|
// p is used for fast JSON parsing
|
|
|
|
p fastjson.Parser
|
|
|
|
|
|
|
|
// buf is used for holding the backing data for Fields
|
|
|
|
buf []byte
|
|
|
|
|
|
|
|
// prefixBuf is used for holding the current key prefix
|
|
|
|
// when it is composed from multiple keys.
|
|
|
|
prefixBuf []byte
|
|
|
|
}
|
|
|
|
|
2024-05-20 04:08:30 +02:00
|
|
|
func (p *JSONParser) reset() {
|
|
|
|
clear(p.Fields)
|
|
|
|
p.Fields = p.Fields[:0]
|
|
|
|
|
2024-05-22 21:01:20 +02:00
|
|
|
p.buf = p.buf[:0]
|
2023-06-21 10:10:45 +02:00
|
|
|
}
|
|
|
|
|
2024-05-20 04:08:30 +02:00
|
|
|
// GetJSONParser returns JSONParser ready to parse JSON lines.
|
2023-06-21 10:10:45 +02:00
|
|
|
//
|
2024-05-20 04:08:30 +02:00
|
|
|
// Return the parser to the pool when it is no longer needed by calling PutJSONParser().
|
|
|
|
func GetJSONParser() *JSONParser {
|
2023-06-21 10:10:45 +02:00
|
|
|
v := parserPool.Get()
|
|
|
|
if v == nil {
|
2024-05-20 04:08:30 +02:00
|
|
|
return &JSONParser{}
|
2023-06-21 10:10:45 +02:00
|
|
|
}
|
2024-05-20 04:08:30 +02:00
|
|
|
return v.(*JSONParser)
|
2023-06-21 10:10:45 +02:00
|
|
|
}
|
|
|
|
|
2024-05-20 04:08:30 +02:00
|
|
|
// PutJSONParser returns the parser to the pool.
|
2023-06-21 10:10:45 +02:00
|
|
|
//
|
|
|
|
// The parser cannot be used after returning to the pool.
|
2024-05-20 04:08:30 +02:00
|
|
|
func PutJSONParser(p *JSONParser) {
|
2023-06-21 10:10:45 +02:00
|
|
|
p.reset()
|
|
|
|
parserPool.Put(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
var parserPool sync.Pool
|
|
|
|
|
|
|
|
// ParseLogMessage parses the given JSON log message msg into p.Fields.
|
|
|
|
//
|
2024-05-20 04:08:30 +02:00
|
|
|
// The p.Fields remains valid until the next call to ParseLogMessage() or PutJSONParser().
|
2024-05-22 21:01:20 +02:00
|
|
|
func (p *JSONParser) ParseLogMessage(msg []byte) error {
|
|
|
|
p.reset()
|
2024-05-20 04:08:30 +02:00
|
|
|
|
2024-05-22 21:01:20 +02:00
|
|
|
msgStr := bytesutil.ToUnsafeString(msg)
|
|
|
|
v, err := p.p.Parse(msgStr)
|
2023-06-21 10:10:45 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot parse json: %w", err)
|
|
|
|
}
|
|
|
|
if t := v.Type(); t != fastjson.TypeObject {
|
|
|
|
return fmt.Errorf("expecting json dictionary; got %s", t)
|
|
|
|
}
|
|
|
|
p.Fields, p.buf, p.prefixBuf = appendLogFields(p.Fields, p.buf, p.prefixBuf, v)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-05-20 04:08:30 +02:00
|
|
|
func appendLogFields(dst []Field, dstBuf, prefixBuf []byte, v *fastjson.Value) ([]Field, []byte, []byte) {
|
2023-06-21 10:10:45 +02:00
|
|
|
o := v.GetObject()
|
|
|
|
o.Visit(func(k []byte, v *fastjson.Value) {
|
|
|
|
t := v.Type()
|
|
|
|
switch t {
|
|
|
|
case fastjson.TypeNull:
|
|
|
|
// Skip nulls
|
|
|
|
case fastjson.TypeObject:
|
|
|
|
// Flatten nested JSON objects.
|
|
|
|
// For example, {"foo":{"bar":"baz"}} is converted to {"foo.bar":"baz"}
|
|
|
|
prefixLen := len(prefixBuf)
|
|
|
|
prefixBuf = append(prefixBuf, k...)
|
|
|
|
prefixBuf = append(prefixBuf, '.')
|
|
|
|
dst, dstBuf, prefixBuf = appendLogFields(dst, dstBuf, prefixBuf, v)
|
|
|
|
prefixBuf = prefixBuf[:prefixLen]
|
|
|
|
case fastjson.TypeArray, fastjson.TypeNumber, fastjson.TypeTrue, fastjson.TypeFalse:
|
|
|
|
// Convert JSON arrays, numbers, true and false values to their string representation
|
|
|
|
dstBufLen := len(dstBuf)
|
|
|
|
dstBuf = v.MarshalTo(dstBuf)
|
|
|
|
value := dstBuf[dstBufLen:]
|
|
|
|
dst, dstBuf = appendLogField(dst, dstBuf, prefixBuf, k, value)
|
|
|
|
case fastjson.TypeString:
|
|
|
|
// Decode JSON strings
|
|
|
|
dstBufLen := len(dstBuf)
|
|
|
|
dstBuf = append(dstBuf, v.GetStringBytes()...)
|
|
|
|
value := dstBuf[dstBufLen:]
|
|
|
|
dst, dstBuf = appendLogField(dst, dstBuf, prefixBuf, k, value)
|
|
|
|
default:
|
|
|
|
logger.Panicf("BUG: unexpected JSON type: %s", t)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return dst, dstBuf, prefixBuf
|
|
|
|
}
|
|
|
|
|
2024-05-20 04:08:30 +02:00
|
|
|
func appendLogField(dst []Field, dstBuf, prefixBuf, k, value []byte) ([]Field, []byte) {
|
2023-06-21 10:10:45 +02:00
|
|
|
dstBufLen := len(dstBuf)
|
|
|
|
dstBuf = append(dstBuf, prefixBuf...)
|
|
|
|
dstBuf = append(dstBuf, k...)
|
|
|
|
name := dstBuf[dstBufLen:]
|
|
|
|
|
2024-05-20 04:08:30 +02:00
|
|
|
dst = append(dst, Field{
|
2023-06-21 10:10:45 +02:00
|
|
|
Name: bytesutil.ToUnsafeString(name),
|
|
|
|
Value: bytesutil.ToUnsafeString(value),
|
|
|
|
})
|
|
|
|
return dst, dstBuf
|
|
|
|
}
|