Skip to content

Commit

Permalink
lokiexporter: support serialization of all body types into JSON (#6639
Browse files Browse the repository at this point in the history
)

* [lokiexporter] Support serialization of all body types into json

* Test that complex, nested bodies are serialized correctly
  • Loading branch information
gregoryfranklin authored Dec 10, 2021
1 parent dd41817 commit 20f6cea
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 20 deletions.
62 changes: 55 additions & 7 deletions exporter/lokiexporter/encode_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,43 @@ import (

type lokiEntry struct {
Name string `json:"name,omitempty"`
Body string `json:"body,omitempty"`
Body json.RawMessage `json:"body,omitempty"`
TraceID string `json:"traceid,omitempty"`
SpanID string `json:"spanid,omitempty"`
Severity string `json:"severity,omitempty"`
Attributes map[string]interface{} `json:"attributes,omitempty"`
Resources map[string]interface{} `json:"resources,omitempty"`
}

func serializeBody(body pdata.AttributeValue) (string, error) {
str := ""
func serializeBody(body pdata.AttributeValue) ([]byte, error) {
var str []byte
var err error
if body.Type() == pdata.AttributeValueTypeString {
str = body.StringVal()
} else {
switch body.Type() {
case pdata.AttributeValueTypeEmpty:
// no body

case pdata.AttributeValueTypeString:
str, err = json.Marshal(body.StringVal())

case pdata.AttributeValueTypeInt:
str, err = json.Marshal(body.IntVal())

case pdata.AttributeValueTypeDouble:
str, err = json.Marshal(body.DoubleVal())

case pdata.AttributeValueTypeBool:
str, err = json.Marshal(body.BoolVal())

case pdata.AttributeValueTypeMap:
str, err = json.Marshal(body.MapVal().AsRaw())

case pdata.AttributeValueTypeArray:
str, err = json.Marshal(attributeValueSliceAsRaw(body.SliceVal()))

case pdata.AttributeValueTypeBytes:
str, err = json.Marshal(body.BytesVal())

default:
err = fmt.Errorf("unsuported body type to serialize")
}
return str, err
Expand All @@ -48,7 +71,7 @@ func encodeJSON(lr pdata.LogRecord, res pdata.Resource) (string, error) {
var logRecord lokiEntry
var jsonRecord []byte
var err error
var body string
var body []byte

body, err = serializeBody(lr.Body())
if err != nil {
Expand All @@ -71,3 +94,28 @@ func encodeJSON(lr pdata.LogRecord, res pdata.Resource) (string, error) {
}
return string(jsonRecord), nil
}

// Copied from pdata (es AttributeValueSlice) asRaw() since its not exported
func attributeValueSliceAsRaw(es pdata.AttributeValueSlice) []interface{} {
rawSlice := make([]interface{}, 0, es.Len())
for i := 0; i < es.Len(); i++ {
v := es.At(i)
switch v.Type() {
case pdata.AttributeValueTypeString:
rawSlice = append(rawSlice, v.StringVal())
case pdata.AttributeValueTypeInt:
rawSlice = append(rawSlice, v.IntVal())
case pdata.AttributeValueTypeDouble:
rawSlice = append(rawSlice, v.DoubleVal())
case pdata.AttributeValueTypeBool:
rawSlice = append(rawSlice, v.BoolVal())
case pdata.AttributeValueTypeBytes:
rawSlice = append(rawSlice, v.BytesVal())
case pdata.AttributeValueTypeEmpty:
rawSlice = append(rawSlice, nil)
default:
rawSlice = append(rawSlice, "<Invalid array value>")
}
}
return rawSlice
}
89 changes: 76 additions & 13 deletions exporter/lokiexporter/encode_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,29 +38,92 @@ func exampleLog() (pdata.LogRecord, pdata.Resource) {
return buffer, resource
}

func exampleJSON() string {
jsonExample := `{"name":"name","body":"Example log","traceid":"01020304000000000000000000000000","spanid":"0506070800000000","severity":"error","attributes":{"attr1":"1","attr2":"2"},"resources":{"host.name":"something"}}`
return jsonExample
}
func TestConvertWithStringBody(t *testing.T) {
in := `{"name":"name","body":"Example log","traceid":"01020304000000000000000000000000","spanid":"0506070800000000","severity":"error","attributes":{"attr1":"1","attr2":"2"},"resources":{"host.name":"something"}}`

func TestConvertString(t *testing.T) {
in := exampleJSON()
out, err := encodeJSON(exampleLog())
t.Log(in)
t.Log(out, err)
assert.NoError(t, err)
assert.Equal(t, in, out)
}

func TestConvertNonString(t *testing.T) {
in := exampleJSON()
func TestConvertWithMapBody(t *testing.T) {
in := `{"name":"name","body":{"key1":"value","key2":"value"},"traceid":"01020304000000000000000000000000","spanid":"0506070800000000","severity":"error","attributes":{"attr1":"1","attr2":"2"},"resources":{"host.name":"something"}}`

log, resource := exampleLog()
mapVal := pdata.NewAttributeValueMap()
mapVal.MapVal().Insert("key1", pdata.NewAttributeValueString("value"))
mapVal.MapVal().Insert("key2", pdata.NewAttributeValueString("value"))
mapVal.CopyTo(log.Body())

out, err := encodeJSON(log, resource)
t.Log(in)
t.Log(out, err)
assert.EqualError(t, err, "unsuported body type to serialize")
assert.NoError(t, err)
assert.Equal(t, in, out)
}

func TestSerializeBody(t *testing.T) {

arrayval := pdata.NewAttributeValueArray()
arrayval.SliceVal().AppendEmpty().SetStringVal("a")
arrayval.SliceVal().AppendEmpty().SetStringVal("b")

simplemap := pdata.NewAttributeValueMap()
simplemap.MapVal().InsertString("key", "val")

complexmap := pdata.NewAttributeValueMap()
complexmap.MapVal().InsertString("keystr", "val")
complexmap.MapVal().InsertInt("keyint", 1)
complexmap.MapVal().InsertDouble("keyint", 1)
complexmap.MapVal().InsertBool("keybool", true)
complexmap.MapVal().InsertNull("keynull")
complexmap.MapVal().Insert("keyarr", arrayval)
complexmap.MapVal().Insert("keymap", simplemap)
complexmap.MapVal().Insert("keyempty", pdata.NewAttributeValueEmpty())

testcases := []struct {
input pdata.AttributeValue
expected []byte
}{
{
pdata.NewAttributeValueEmpty(),
nil,
},
{
pdata.NewAttributeValueString("a"),
[]byte(`"a"`),
},
{
pdata.NewAttributeValueInt(1),
[]byte(`1`),
},
{
pdata.NewAttributeValueDouble(1.1),
[]byte(`1.1`),
},
{
pdata.NewAttributeValueBool(true),
[]byte(`true`),
},
{
simplemap,
[]byte(`{"key":"val"}`),
},
{
complexmap,
[]byte(`{"keyarr":["a","b"],"keybool":true,"keyempty":null,"keyint":1,"keymap":{"key":"val"},"keynull":null,"keystr":"val"}`),
},
{
arrayval,
[]byte(`["a","b"]`),
},
{
pdata.NewAttributeValueBytes([]byte(`abc`)),
[]byte(`"YWJj"`),
},
}

for _, test := range testcases {
out, err := serializeBody(test.input)
assert.NoError(t, err)
assert.Equal(t, test.expected, out)
}
}

0 comments on commit 20f6cea

Please sign in to comment.