From 20f6cead5bcfadb9e82463b2134aa28c777164ac Mon Sep 17 00:00:00 2001 From: gregoryfranklin <45693481+gregoryfranklin@users.noreply.github.com> Date: Fri, 10 Dec 2021 11:46:16 +0000 Subject: [PATCH] `lokiexporter`: support serialization of all body types into JSON (#6639) * [lokiexporter] Support serialization of all body types into json * Test that complex, nested bodies are serialized correctly --- exporter/lokiexporter/encode_json.go | 62 ++++++++++++++-- exporter/lokiexporter/encode_json_test.go | 89 +++++++++++++++++++---- 2 files changed, 131 insertions(+), 20 deletions(-) diff --git a/exporter/lokiexporter/encode_json.go b/exporter/lokiexporter/encode_json.go index 5c09a5e2dee6..39b8e34c3f89 100644 --- a/exporter/lokiexporter/encode_json.go +++ b/exporter/lokiexporter/encode_json.go @@ -25,7 +25,7 @@ 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"` @@ -33,12 +33,35 @@ type lokiEntry struct { 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 @@ -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 { @@ -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, "") + } + } + return rawSlice +} diff --git a/exporter/lokiexporter/encode_json_test.go b/exporter/lokiexporter/encode_json_test.go index d54f234bb185..328b70c94f6d 100644 --- a/exporter/lokiexporter/encode_json_test.go +++ b/exporter/lokiexporter/encode_json_test.go @@ -38,21 +38,17 @@ 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")) @@ -60,7 +56,74 @@ func TestConvertNonString(t *testing.T) { 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) + } }