-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
LokI Exporter - Adding a feature for loki exporter to encode JSON for log entry #3874
Changes from 10 commits
491e6e1
18d1808
403b6a1
4c695b7
caa4507
071a13c
bb37818
728f4bf
bf30cc5
bb253b3
11d5439
fcdb38b
e01ed0f
ebdcccb
45a52eb
e80de58
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package lokiexporter | ||
|
||
import ( | ||
"encoding/json" | ||
|
||
"go.opentelemetry.io/collector/consumer/pdata" | ||
tracetranslator "go.opentelemetry.io/collector/translator/trace" | ||
) | ||
|
||
// JSON representation of the LogRecord as described by https://developers.google.com/protocol-buffers/docs/proto3#json | ||
|
||
type lokiEntry struct { | ||
Name string `json:"name,omitempty"` | ||
Body string `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"` | ||
} | ||
|
||
func encodeJSON(lr pdata.LogRecord) (string, error) { | ||
var logRecord lokiEntry | ||
var jsonRecord []byte | ||
|
||
logRecord = lokiEntry{ | ||
Name: lr.Name(), | ||
Body: lr.Body().StringVal(), | ||
TraceID: lr.TraceID().HexString(), | ||
SpanID: lr.SpanID().HexString(), | ||
Severity: lr.SeverityText(), | ||
Attributes: tracetranslator.AttributeMapToMap(lr.Attributes())} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As you created a full structure, why not add "Resources" ? Maybe conditionaly ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about nested structures on Grafana / Loki ? Does anyone can confirm ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Working on logs enriched by a resourcedetector processor, I confirm the Resources should be converted to structured logs data ! |
||
|
||
jsonRecord, err := json.Marshal(logRecord) | ||
if err != nil { | ||
return "", err | ||
} | ||
return string(jsonRecord), nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package lokiexporter | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"go.opentelemetry.io/collector/consumer/pdata" | ||
) | ||
|
||
func exampleLog() pdata.LogRecord { | ||
|
||
buffer := pdata.NewLogRecord() | ||
buffer.Body().SetStringVal("Example log") | ||
buffer.SetName("name") | ||
buffer.SetSeverityText("error") | ||
buffer.Attributes().Insert("attr1", pdata.NewAttributeValueString("1")) | ||
buffer.Attributes().Insert("attr2", pdata.NewAttributeValueString("2")) | ||
buffer.SetTraceID(pdata.NewTraceID([16]byte{1, 2, 3, 4})) | ||
buffer.SetSpanID(pdata.NewSpanID([8]byte{5, 6, 7, 8})) | ||
|
||
return buffer | ||
} | ||
|
||
func exampleJson() string { | ||
jsonExample := `{"name":"name","body":"Example log","traceid":"01020304000000000000000000000000","spanid":"0506070800000000","severity":"error","attributes":{"attr1":"1","attr2":"2"}}` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To follow some logic and to be more explicit on loki side, why not prefix your arbitrary keys (name, body, traceid...) by someting like "otel_collector_lokiexporter_" ? I would prefer use "otel.collector.lokiexporter." to be more consistent with OTEL but if you apply |
||
return jsonExample | ||
} | ||
|
||
func TestConvert(t *testing.T) { | ||
in := exampleJson() | ||
out, err := encodeJSON(exampleLog()) | ||
t.Log(in) | ||
t.Log(out, err) | ||
assert.Equal(t, in, out) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -135,7 +135,19 @@ func (l *lokiExporter) logDataToLoki(ld pdata.Logs) (pr *logproto.PushRequest, n | |
continue | ||
} | ||
labels := mergedLabels.String() | ||
entry := convertLogToLokiEntry(log) | ||
var entry *logproto.Entry | ||
if l.config.Format == "json" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the decision on format is not made at runtime, but rather at config time, what are your thoughts on making this decision early on and mapping the appropriate method ahead of time? This would remove the additional check per record. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gramidt I have just pushed a new commit following your comments. |
||
var err error | ||
entry, err = convertLogToJsonEntry(log) | ||
if err != nil { | ||
// Couldn't convert to JSON so dropping log. | ||
numDroppedLogs++ | ||
l.logger.Error("Failed to convert to JSON - Dropping Log", zap.Error(err)) | ||
continue | ||
} | ||
} else { | ||
entry = convertLogToLokiEntry(log) | ||
} | ||
|
||
if stream, ok := streams[labels]; ok { | ||
stream.Entries = append(stream.Entries, *entry) | ||
|
@@ -201,3 +213,14 @@ func convertLogToLokiEntry(lr pdata.LogRecord) *logproto.Entry { | |
Line: lr.Body().StringVal(), | ||
} | ||
} | ||
|
||
func convertLogToJsonEntry(lr pdata.LogRecord) (*logproto.Entry, error) { | ||
line, err := encodeJSON(lr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &logproto.Entry{ | ||
Timestamp: time.Unix(0, int64(lr.Timestamp())), | ||
Line: line, | ||
}, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it, finaly it's not too intrusive as potential big change !