Skip to content

Commit af160a4

Browse files
jrigueraCemDK
andcommitted
[recevier/cloudfoundryreceiver] WIP: Reimplement function to parse RTR log lines extracting fields and add tests
Co-authored-by: Cem Deniz Kabakci <[email protected]>
1 parent 6dcf1fc commit af160a4

File tree

3 files changed

+144
-42
lines changed

3 files changed

+144
-42
lines changed

receiver/cloudfoundryreceiver/converter.go

+98-36
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
package cloudfoundryreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/cloudfoundryreceiver"
55

66
import (
7+
"fmt"
78
"strings"
89
"time"
10+
"unicode"
911

1012
"code.cloudfoundry.org/go-loggregator/rpc/loggregator_v2"
1113
"go.opentelemetry.io/collector/pdata/pcommon"
@@ -15,7 +17,11 @@ import (
1517
)
1618

1719
const (
18-
attributeNamePrefix = "org.cloudfoundry."
20+
attributeNamePrefix = "org.cloudfoundry."
21+
envelopeSourceTypeTag = "org.cloudfoundry.source_type"
22+
envelopeSourceTypeValueRTR = "RTR"
23+
logLineRTRTraceIDKey = "x_b3_traceid"
24+
logLineRTRSpanIDKey = "x_b3_spanid"
1925
)
2026

2127
func convertEnvelopeToMetrics(envelope *loggregator_v2.Envelope, metricSlice pmetric.MetricSlice, startTime time.Time) {
@@ -43,9 +49,8 @@ func convertEnvelopeToMetrics(envelope *loggregator_v2.Envelope, metricSlice pme
4349
}
4450
}
4551

46-
func convertEnvelopeToLogs(envelope *loggregator_v2.Envelope, logSlice plog.LogRecordSlice, startTime time.Time) {
47-
switch envelope.Message.(type) {
48-
case *loggregator_v2.Envelope_Log:
52+
func convertEnvelopeToLogs(envelope *loggregator_v2.Envelope, logSlice plog.LogRecordSlice, startTime time.Time) error {
53+
if _, isLog := envelope.Message.(*loggregator_v2.Envelope_Log); isLog {
4954
log := logSlice.AppendEmpty()
5055
log.SetTimestamp(pcommon.Timestamp(envelope.GetTimestamp()))
5156
log.SetObservedTimestamp(pcommon.NewTimestampFromTime(startTime))
@@ -59,9 +64,30 @@ func convertEnvelopeToLogs(envelope *loggregator_v2.Envelope, logSlice plog.LogR
5964
log.SetSeverityNumber(plog.SeverityNumberError)
6065
}
6166
copyEnvelopeAttributes(log.Attributes(), envelope)
62-
_ = parseLogTracingFields(log)
63-
default:
67+
if value, found := log.Attributes().Get(envelopeSourceTypeTag); found && value.AsString() == envelopeSourceTypeValueRTR {
68+
_, wordsMap := parseLogLine(log.Body().AsString())
69+
traceIDStr, found := wordsMap[logLineRTRTraceIDKey]
70+
if !found {
71+
return fmt.Errorf("traceid key %s not found in log", logLineRTRTraceIDKey)
72+
}
73+
spanIDStr, found := wordsMap[logLineRTRSpanIDKey]
74+
if !found {
75+
return fmt.Errorf("spanid key %s not found in log", logLineRTRSpanIDKey)
76+
}
77+
traceID, err := trace.TraceIDFromHex(traceIDStr)
78+
if err != nil {
79+
return err
80+
}
81+
spanID, err := trace.SpanIDFromHex(spanIDStr)
82+
if err != nil {
83+
return err
84+
}
85+
log.SetTraceID([16]byte(traceID))
86+
log.SetSpanID([8]byte(spanID))
87+
}
88+
return nil
6489
}
90+
return fmt.Errorf("envelope is not a log")
6591
}
6692

6793
func copyEnvelopeAttributes(attributes pcommon.Map, envelope *loggregator_v2.Envelope) {
@@ -78,37 +104,73 @@ func copyEnvelopeAttributes(attributes pcommon.Map, envelope *loggregator_v2.Env
78104
}
79105
}
80106

81-
func parseLogTracingFields(log plog.LogRecord) error {
82-
if value, found := log.Attributes().Get("org.cloudfoundry.source_type"); !found || value.AsString() != "RTR" {
83-
return nil
84-
}
85-
s := log.Body().AsString()
86-
quoted := false
87-
a := strings.FieldsFunc(s, func(r rune) bool {
88-
if r == '"' {
89-
quoted = !quoted
107+
func parseLogLine(s string) ([]string, map[string]string) {
108+
wordList := make([]string, 0, 20)
109+
sb := &strings.Builder{}
110+
mapValue := &strings.Builder{}
111+
timestamp := &strings.Builder{}
112+
isTimeStamp := false
113+
mapKey := ""
114+
isMap := false
115+
isQuoted := false
116+
wordMap := make(map[string]string)
117+
for _, ch := range s {
118+
if ch == '"' {
119+
isQuoted = !isQuoted
120+
sb.WriteRune(ch)
121+
continue
90122
}
91-
return !quoted && r == ' '
92-
})
93-
94-
traceIDStr := strings.Split(a[21], ":")[1]
95-
traceIDStr = strings.Trim(traceIDStr, "\"")
96-
97-
spanIDStr := strings.Split(a[22], ":")[1]
98-
spanIDStr = strings.Trim(spanIDStr, "\"")
99-
100-
traceID, err := trace.TraceIDFromHex(traceIDStr)
101-
if err != nil {
102-
return err
123+
if isQuoted {
124+
sb.WriteRune(ch)
125+
if isMap {
126+
mapValue.WriteRune(ch)
127+
}
128+
continue
129+
}
130+
if ch == '[' && sb.Len() == 0 {
131+
// first char after space
132+
isTimeStamp = true
133+
continue
134+
}
135+
if ch == ']' && isTimeStamp {
136+
wordList = append(wordList, timestamp.String())
137+
timestamp.Reset()
138+
isTimeStamp = false
139+
continue
140+
}
141+
if isTimeStamp {
142+
timestamp.WriteRune(ch)
143+
continue
144+
}
145+
if unicode.IsSpace(ch) {
146+
if sb.Len() > 0 {
147+
word := sb.String()
148+
if isMap {
149+
wordMap[mapKey] = mapValue.String()
150+
} else if strings.HasPrefix(word, `"`) && strings.HasSuffix(word, `"`) {
151+
// remove " if the item is not a keyMap and starts and ends with it
152+
word = strings.Trim(word, `"`)
153+
}
154+
wordList = append(wordList, word)
155+
}
156+
isMap = false
157+
mapValue.Reset()
158+
sb.Reset()
159+
continue
160+
}
161+
if isMap {
162+
mapValue.WriteRune(ch)
163+
} else if ch == ':' {
164+
mapKey = sb.String()
165+
isMap = true
166+
}
167+
sb.WriteRune(ch)
103168
}
104-
105-
spanID, err := trace.SpanIDFromHex(spanIDStr)
106-
if err != nil {
107-
return err
169+
if sb.Len() > 0 {
170+
wordList = append(wordList, sb.String())
171+
if isMap {
172+
wordMap[mapKey] = mapValue.String()
173+
}
108174
}
109-
110-
log.SetTraceID([16]byte(traceID))
111-
log.SetSpanID([8]byte(spanID))
112-
113-
return nil
175+
return wordList, wordMap
114176
}

receiver/cloudfoundryreceiver/converter_test.go

+45-5
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,49 @@ func TestConvertGaugeEnvelope(t *testing.T) {
143143
assertAttributes(t, dataPoint.Attributes(), expectedAttributes)
144144
}
145145

146+
func TestParseLogLine(t *testing.T) {
147+
logLines := []string{
148+
`www.example.com - [2024-05-21T15:40:13.892179798Z] "GET /articles/ssdfws HTTP/1.1" 200 0 110563 "-" "python-requests/2.26.0" "20.191.2.244:52238" "10.88.195.81:61222" x_forwarded_for:"18.21.57.150, 10.28.45.29, 35.16.25.46, 20.191.2.244" x_forwarded_proto:"https" vcap_request_id:"766afb19-1779-4bb9-65d4-f01306f9f912" response_time:0.191835 gorouter_time:0.000139 app_id:"e3267823-0938-43ce-85ff-003e3e3a5a23" app_index:"4" instance_id:"918dd283-a0ed-48be-7f0c-253b" x_cf_routererror:"-" x_forwarded_host:"www.example.com" x_b3_traceid:"766afb1917794bb965d4f01306f9f912" x_b3_spanid:"65d4f01306f9f912" x_b3_parentspanid:"-" b3:"766afb1917794bb965d4f01306f9f912-65d4f01306f9f912" traceparent:"00-766afb1917794bb965d4f01306f9f912-65d4f01306f9f912-01" tracestate:"gorouter=65d4f01306f9f912"`,
149+
}
150+
wordListExpected := [][]string{
151+
{"www.example.com", "-", "2024-05-21T15:40:13.892179798Z", "GET /articles/ssdfws HTTP/1.1", "200", "0", "110563", "-", "python-requests/2.26.0", "20.191.2.244:52238", "10.88.195.81:61222", `x_forwarded_for:"18.21.57.150, 10.28.45.29, 35.16.25.46, 20.191.2.244"`, `x_forwarded_proto:"https"`, `vcap_request_id:"766afb19-1779-4bb9-65d4-f01306f9f912"`, `response_time:0.191835`, `gorouter_time:0.000139`, `app_id:"e3267823-0938-43ce-85ff-003e3e3a5a23"`, `app_index:"4"`, `instance_id:"918dd283-a0ed-48be-7f0c-253b"`, `x_cf_routererror:"-"`, `x_forwarded_host:"www.example.com"`, `x_b3_traceid:"766afb1917794bb965d4f01306f9f912"`, `x_b3_spanid:"65d4f01306f9f912"`, `x_b3_parentspanid:"-"`, `b3:"766afb1917794bb965d4f01306f9f912-65d4f01306f9f912"`, `traceparent:"00-766afb1917794bb965d4f01306f9f912-65d4f01306f9f912-01"`, `tracestate:"gorouter=65d4f01306f9f912"`},
152+
}
153+
wordMapExpected := []map[string]string{
154+
{
155+
"x_forwarded_for": "18.21.57.150, 10.28.45.29, 35.16.25.46, 20.191.2.244",
156+
"x_forwarded_proto": "https",
157+
"vcap_request_id": "766afb19-1779-4bb9-65d4-f01306f9f912",
158+
"response_time": "0.191835",
159+
"gorouter_time": "0.000139",
160+
"app_id": "e3267823-0938-43ce-85ff-003e3e3a5a23",
161+
"app_index": "4",
162+
"instance_id": "918dd283-a0ed-48be-7f0c-253b",
163+
"x_cf_routererror": "-",
164+
"x_forwarded_host": "www.example.com",
165+
"x_b3_traceid": "766afb1917794bb965d4f01306f9f912",
166+
"x_b3_spanid": "65d4f01306f9f912",
167+
"x_b3_parentspanid": "-",
168+
"b3": "766afb1917794bb965d4f01306f9f912-65d4f01306f9f912",
169+
"traceparent": "00-766afb1917794bb965d4f01306f9f912-65d4f01306f9f912-01",
170+
"tracestate": "gorouter=65d4f01306f9f912",
171+
},
172+
}
173+
for index, logLine := range logLines {
174+
wordList, wordMap := parseLogLine(logLine)
175+
require.Equal(t, len(wordList), len(wordListExpected[index]))
176+
require.Equal(t, len(wordMap), len(wordMapExpected[index]))
177+
178+
for wordExpectedIndex, wordExpected := range wordListExpected[index] {
179+
assert.Equal(t, wordExpected, wordList[wordExpectedIndex], "List Item %s value", wordList[wordExpectedIndex])
180+
}
181+
for k, v := range wordMapExpected[index] {
182+
value, present := wordMap[k]
183+
assert.True(t, present, "Map Item %s presence", k)
184+
assert.Equal(t, v, value, "Map Item %s value", v)
185+
}
186+
}
187+
}
188+
146189
func TestConvertLogsEnvelope(t *testing.T) {
147190
now := time.Now()
148191
before := time.Now().Add(-time.Second)
@@ -166,17 +209,14 @@ func TestConvertLogsEnvelope(t *testing.T) {
166209
}
167210

168211
logSlice := plog.NewLogRecordSlice()
169-
170-
convertEnvelopeToLogs(&envelope, logSlice, now)
171-
212+
e := convertEnvelopeToLogs(&envelope, logSlice, now)
213+
require.Equal(t, nil, e)
172214
require.Equal(t, 1, logSlice.Len())
173-
174215
log := logSlice.At(0)
175216
assert.Equal(t, "log message payload", log.Body().AsString())
176217
assert.Equal(t, plog.SeverityNumberInfo.String(), log.SeverityText())
177218
assert.Equal(t, pcommon.NewTimestampFromTime(before), log.Timestamp())
178219
assert.Equal(t, pcommon.NewTimestampFromTime(now), log.ObservedTimestamp())
179-
180220
assertAttributes(t, log.Attributes(), map[string]string{
181221
"org.cloudfoundry.source_id": "uaa",
182222
"org.cloudfoundry.origin": "gorouter",

receiver/cloudfoundryreceiver/receiver.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ func (cfr *cloudFoundryReceiver) streamLogs(
218218
observedTime := time.Now()
219219
for _, envelope := range envelopes {
220220
if envelope != nil {
221-
convertEnvelopeToLogs(envelope, libraryLogs, observedTime)
221+
_ = convertEnvelopeToLogs(envelope, libraryLogs, observedTime)
222222
}
223223
}
224224

0 commit comments

Comments
 (0)