6
6
package log
7
7
8
8
import (
9
- "bytes"
10
9
"context"
10
+ "strings"
11
+ "sync"
11
12
12
13
"github.com/cockroachdb/cockroach/pkg/cli/exit"
13
14
"github.com/cockroachdb/cockroach/pkg/util/log/logconfig"
@@ -31,17 +32,29 @@ const (
31
32
logAttributeSinkKey = "sink.name"
32
33
)
33
34
35
+ // pool for OTEL spec log record objects that we can reuse between requests
36
+ var otlpLogRecordPool = sync.Pool {
37
+ New : func () any {
38
+ return & lpb.LogRecord {
39
+ Body : & cpb.AnyValue {
40
+ Value : & cpb.AnyValue_StringValue {StringValue : "" },
41
+ },
42
+ }
43
+ },
44
+ }
45
+
34
46
// OpenTelemetry log sink
35
47
type otlpSink struct {
36
- conn * grpc.ClientConn
37
- lsc collpb.LogsServiceClient
38
- name string
39
- resource * rpb.Resource
48
+ conn * grpc.ClientConn
49
+ lsc collpb.LogsServiceClient
50
+
51
+ // requestObject should not be modified concurrently as it is reused
52
+ // between requests
53
+ requestObject * collpb.ExportLogsServiceRequest
40
54
}
41
55
42
56
func newOTLPSink (config logconfig.OTLPSinkConfig ) (* otlpSink , error ) {
43
57
dialOpts := []grpc.DialOption {
44
- grpc .WithUserAgent ("CRDB OTLP over gRPC logs exporter" ),
45
58
grpc .WithTransportCredentials (insecure .NewCredentials ()),
46
59
}
47
60
@@ -55,24 +68,35 @@ func newOTLPSink(config logconfig.OTLPSinkConfig) (*otlpSink, error) {
55
68
}
56
69
57
70
lsc := collpb .NewLogsServiceClient (conn )
58
- name := config .SinkName
59
- return & otlpSink {
71
+ sink := & otlpSink {
60
72
conn : conn ,
61
73
lsc : lsc ,
62
- name : name ,
63
- resource : & rpb.Resource {
64
- Attributes : []* cpb.KeyValue {
74
+ requestObject : & collpb.ExportLogsServiceRequest {
75
+ ResourceLogs : []* lpb.ResourceLogs {
65
76
{
66
- Key : logAttributeServiceKey ,
67
- Value : & cpb.AnyValue {Value : & cpb.AnyValue_StringValue {StringValue : logAttributeServiceValue }},
68
- },
69
- {
70
- Key : logAttributeSinkKey ,
71
- Value : & cpb.AnyValue {Value : & cpb.AnyValue_StringValue {StringValue : name }},
77
+ Resource : & rpb.Resource {
78
+ Attributes : []* cpb.KeyValue {
79
+ {
80
+ Key : logAttributeServiceKey ,
81
+ Value : & cpb.AnyValue {Value : & cpb.AnyValue_StringValue {StringValue : logAttributeServiceValue }},
82
+ },
83
+ {
84
+ Key : logAttributeSinkKey ,
85
+ Value : & cpb.AnyValue {Value : & cpb.AnyValue_StringValue {StringValue : config .SinkName }},
86
+ },
87
+ },
88
+ },
89
+ InstrumentationLibraryLogs : []* lpb.InstrumentationLibraryLogs {
90
+ {
91
+ Logs : nil ,
92
+ },
93
+ },
72
94
},
73
95
},
74
96
},
75
- }, nil
97
+ }
98
+
99
+ return sink , nil
76
100
}
77
101
78
102
func (sink * otlpSink ) isNotShutdown () bool {
@@ -91,43 +115,47 @@ func (sink *otlpSink) exitCode() exit.Code {
91
115
return exit .LoggingNetCollectorUnavailable ()
92
116
}
93
117
94
- // converts the raw bytes into OTEL log records
95
- func extractRecordsToOTLP (b []byte ) []* lpb.LogRecord {
96
- bodies := bytes .Split (b , []byte ("\n " ))
97
- records := make ([]* lpb.LogRecord , 0 , len (bodies ))
98
-
99
- for _ , body := range bodies {
100
- body = bytes .TrimSpace (body )
101
- if len (body ) > 0 {
102
- records = append (records , & lpb.LogRecord {
103
- Body : & cpb.AnyValue {
104
- Value : & cpb.AnyValue_StringValue {
105
- StringValue : string (body ),
106
- },
107
- },
108
- })
118
+ // converts the raw bytes into OTEL log records using the otlpLogRecordPool
119
+ func otlpExtractRecords (b []byte ) []* lpb.LogRecord {
120
+ body := string (b )
121
+ records := make ([]* lpb.LogRecord , 0 , strings .Count (body , "\n " )+ 1 )
122
+
123
+ start := 0
124
+ for i , ch := range body {
125
+ if ch == '\n' {
126
+ if i > start {
127
+ record := otlpLogRecordPool .Get ().(* lpb.LogRecord )
128
+ record .Body .Value .(* cpb.AnyValue_StringValue ).StringValue = body [start :i ]
129
+ records = append (records , record )
130
+ }
131
+ start = i + 1
109
132
}
110
133
}
111
134
135
+ // check at the very end to ensure entire buffer is processed
136
+ if start < len (body ) {
137
+ record := otlpLogRecordPool .Get ().(* lpb.LogRecord )
138
+ record .Body .Value .(* cpb.AnyValue_StringValue ).StringValue = body [start :]
139
+ records = append (records , record )
140
+ }
141
+
112
142
return records
113
143
}
114
144
115
145
func (sink * otlpSink ) output (b []byte , opts sinkOutputOptions ) error {
116
- records := extractRecordsToOTLP (b )
117
146
ctx := context .Background ()
118
147
119
- _ , err := sink .lsc .Export (ctx , & collpb.ExportLogsServiceRequest {
120
- ResourceLogs : []* lpb.ResourceLogs {
121
- {
122
- Resource : sink .resource ,
123
- InstrumentationLibraryLogs : []* lpb.InstrumentationLibraryLogs {
124
- {
125
- Logs : records ,
126
- },
127
- },
128
- },
129
- },
130
- })
148
+ records := otlpExtractRecords (b )
149
+ sink .requestObject .ResourceLogs [0 ].InstrumentationLibraryLogs [0 ].Logs = records
150
+
151
+ // transmit the log over the network
152
+ _ , err := sink .lsc .Export (ctx , sink .requestObject )
153
+
154
+ // put the records back into the pool
155
+ for _ , record := range records {
156
+ record .Body .Value .(* cpb.AnyValue_StringValue ).StringValue = ""
157
+ otlpLogRecordPool .Put (record )
158
+ }
131
159
132
160
if status .Code (err ) == codes .OK {
133
161
return nil
0 commit comments