scout_apm_logging 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/lib/scout_apm/logging/loggers/formatter.rb +1 -1
- data/lib/scout_apm/logging/loggers/opentelemetry/exporter/exporter/otlp/logs_exporter.rb +102 -93
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/batch_log_record_processor.rb +1 -3
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record.rb +56 -1
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_data.rb +1 -1
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_limits.rb +49 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_processor.rb +2 -3
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger.rb +7 -3
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger_provider.rb +15 -3
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/version.rb +2 -2
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs.rb +1 -0
- data/lib/scout_apm/logging/version.rb +1 -1
- data/scout_apm_logging.gemspec +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae315d1586cbc29835019dd459208978f033398a7427dedd7dd36b49c7fd6296
|
4
|
+
data.tar.gz: 734acfcbaae4e9a424710889d30a2cde7cc6904d4e56d304ac173a327e8c1fd0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36adc77a85764f440b3bf8170528c28d6957e82c8e729bc03dd74a533f0c9d9c4a6231e5aae35dbad5d92a2cb77f446f004770c5998cb4559220899c147cd239
|
7
|
+
data.tar.gz: d89fdad7d8d5efe7678055180fe28e3217b5adbd8cdcbfb70cee68fd93ee9e9c8b77ed56c6e19088df8919c55dbbd8a60a69e6dac5f4b597047271399d9f4f9b
|
data/CHANGELOG.md
CHANGED
@@ -36,7 +36,7 @@ module ScoutApm
|
|
36
36
|
).on_emit(
|
37
37
|
severity_text: severity,
|
38
38
|
severity_number: ::Logger::Severity.const_get(severity),
|
39
|
-
attributes: attributes_to_log,
|
39
|
+
attributes: attributes_to_log.transform_keys(&:to_s),
|
40
40
|
timestamp: time,
|
41
41
|
body: msg,
|
42
42
|
context: ::OpenTelemetry::Context.current
|
@@ -27,52 +27,53 @@ module ScoutApm
|
|
27
27
|
SUCCESS = OpenTelemetry::SDK::Logs::Export::SUCCESS
|
28
28
|
FAILURE = OpenTelemetry::SDK::Logs::Export::FAILURE
|
29
29
|
private_constant(:SUCCESS, :FAILURE)
|
30
|
-
|
30
|
+
|
31
31
|
# Default timeouts in seconds.
|
32
32
|
KEEP_ALIVE_TIMEOUT = 30
|
33
33
|
RETRY_COUNT = 5
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
private_constant(:KEEP_ALIVE_TIMEOUT, :RETRY_COUNT)
|
35
|
+
|
37
36
|
ERROR_MESSAGE_INVALID_HEADERS = 'headers must be a String with comma-separated URL Encoded UTF-8 k=v pairs or a Hash'
|
38
37
|
private_constant(:ERROR_MESSAGE_INVALID_HEADERS)
|
39
|
-
|
38
|
+
|
40
39
|
DEFAULT_USER_AGENT = "OTel-OTLP-Exporter-Ruby/#{OpenTelemetry::Exporter::OTLP::VERSION} Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM}; #{RUBY_ENGINE}/#{RUBY_ENGINE_VERSION})".freeze
|
41
|
-
|
40
|
+
|
42
41
|
def self.ssl_verify_mode
|
43
|
-
if ENV
|
42
|
+
if ENV['OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_PEER'] == 'true'
|
44
43
|
OpenSSL::SSL::VERIFY_PEER
|
45
|
-
elsif ENV
|
44
|
+
elsif ENV['OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_NONE'] == 'true'
|
46
45
|
OpenSSL::SSL::VERIFY_NONE
|
47
46
|
else
|
48
47
|
OpenSSL::SSL::VERIFY_PEER
|
49
48
|
end
|
50
49
|
end
|
51
|
-
|
50
|
+
|
52
51
|
def initialize(endpoint: ::OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_LOGS_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT', default: 'http://localhost:4318/v1/logs'),
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
52
|
+
certificate_file: ::OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE', 'OTEL_EXPORTER_OTLP_CERTIFICATE'),
|
53
|
+
client_certificate_file: ::OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE', 'OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE'),
|
54
|
+
client_key_file: ::OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY', 'OTEL_EXPORTER_OTLP_CLIENT_KEY'),
|
55
|
+
ssl_verify_mode: LogsExporter.ssl_verify_mode,
|
56
|
+
headers: ::OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_LOGS_HEADERS', 'OTEL_EXPORTER_OTLP_HEADERS', default: {}),
|
57
|
+
compression: ::OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_LOGS_COMPRESSION', 'OTEL_EXPORTER_OTLP_COMPRESSION', default: 'gzip'),
|
58
|
+
timeout: ::OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_LOGS_TIMEOUT', 'OTEL_EXPORTER_OTLP_TIMEOUT', default: 10))
|
59
|
+
raise ArgumentError, "invalid url for OTLP::Logs::LogsExporter #{endpoint}" unless ::OpenTelemetry::Common::Utilities.valid_url?(endpoint)
|
59
60
|
raise ArgumentError, "unsupported compression key #{compression}" unless compression.nil? || %w[gzip none].include?(compression)
|
60
|
-
|
61
|
+
|
61
62
|
@uri = if endpoint == ENV['OTEL_EXPORTER_OTLP_ENDPOINT']
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
@http = http_connection(@uri, ssl_verify_mode, certificate_file)
|
68
|
-
|
63
|
+
URI.join(endpoint, 'v1/logs')
|
64
|
+
else
|
65
|
+
URI(endpoint)
|
66
|
+
end
|
67
|
+
|
68
|
+
@http = http_connection(@uri, ssl_verify_mode, certificate_file, client_certificate_file, client_key_file)
|
69
|
+
|
69
70
|
@path = @uri.path
|
70
71
|
@headers = prepare_headers(headers)
|
71
72
|
@timeout = timeout.to_f
|
72
73
|
@compression = compression
|
73
74
|
@shutdown = false
|
74
75
|
end
|
75
|
-
|
76
|
+
|
76
77
|
# Called to export sampled {OpenTelemetry::SDK::Logs::LogRecordData} structs.
|
77
78
|
#
|
78
79
|
# @param [Enumerable<OpenTelemetry::SDK::Logs::LogRecordData>] log_record_data the
|
@@ -83,10 +84,10 @@ module ScoutApm
|
|
83
84
|
def export(log_record_data, timeout: nil)
|
84
85
|
OpenTelemetry.logger.error('Logs Exporter tried to export, but it has already shut down') if @shutdown
|
85
86
|
return FAILURE if @shutdown
|
86
|
-
|
87
|
+
|
87
88
|
send_bytes(encode(log_record_data), timeout: timeout)
|
88
89
|
end
|
89
|
-
|
90
|
+
|
90
91
|
# Called when {OpenTelemetry::SDK::Logs::LoggerProvider#force_flush} is called, if
|
91
92
|
# this exporter is registered to a {OpenTelemetry::SDK::Logs::LoggerProvider}
|
92
93
|
# object.
|
@@ -95,7 +96,7 @@ module ScoutApm
|
|
95
96
|
def force_flush(timeout: nil)
|
96
97
|
SUCCESS
|
97
98
|
end
|
98
|
-
|
99
|
+
|
99
100
|
# Called when {OpenTelemetry::SDK::Logs::LoggerProvider#shutdown} is called, if
|
100
101
|
# this exporter is registered to a {OpenTelemetry::SDK::Logs::LoggerProvider}
|
101
102
|
# object.
|
@@ -106,18 +107,24 @@ module ScoutApm
|
|
106
107
|
@http.finish if @http.started?
|
107
108
|
SUCCESS
|
108
109
|
end
|
109
|
-
|
110
|
+
|
110
111
|
private
|
111
|
-
|
112
|
-
def
|
112
|
+
|
113
|
+
def handle_http_error(response)
|
114
|
+
OpenTelemetry.handle_error(message: "OTLP logs exporter received #{response.class.name}, http.code=#{response.code}, for uri: '#{@path}'")
|
115
|
+
end
|
116
|
+
|
117
|
+
def http_connection(uri, ssl_verify_mode, certificate_file, client_certificate_file, client_key_file)
|
113
118
|
http = Net::HTTP.new(uri.host, uri.port)
|
114
119
|
http.use_ssl = uri.scheme == 'https'
|
115
120
|
http.verify_mode = ssl_verify_mode
|
116
121
|
http.ca_file = certificate_file unless certificate_file.nil?
|
122
|
+
http.cert = OpenSSL::X509::Certificate.new(File.read(client_certificate_file)) unless client_certificate_file.nil?
|
123
|
+
http.key = OpenSSL::PKey::RSA.new(File.read(client_key_file)) unless client_key_file.nil?
|
117
124
|
http.keep_alive_timeout = KEEP_ALIVE_TIMEOUT
|
118
125
|
http
|
119
126
|
end
|
120
|
-
|
127
|
+
|
121
128
|
# The around_request is a private method that provides an extension
|
122
129
|
# point for the exporters network calls. The default behaviour
|
123
130
|
# is to not record these operations.
|
@@ -128,10 +135,10 @@ module ScoutApm
|
|
128
135
|
def around_request
|
129
136
|
::OpenTelemetry::Common::Utilities.untraced { yield } # rubocop:disable Style/ExplicitBlockArgument
|
130
137
|
end
|
131
|
-
|
138
|
+
|
132
139
|
def send_bytes(bytes, timeout:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
133
140
|
return FAILURE if bytes.nil?
|
134
|
-
|
141
|
+
|
135
142
|
request = Net::HTTP::Post.new(@path)
|
136
143
|
if @compression == 'gzip'
|
137
144
|
request.add_field('Content-Encoding', 'gzip')
|
@@ -139,39 +146,41 @@ module ScoutApm
|
|
139
146
|
else
|
140
147
|
body = bytes
|
141
148
|
end
|
142
|
-
|
149
|
+
|
143
150
|
request.body = body
|
144
151
|
request.add_field('Content-Type', 'application/x-protobuf')
|
145
152
|
@headers.each { |key, value| request.add_field(key, value) }
|
146
|
-
|
153
|
+
|
147
154
|
retry_count = 0
|
148
155
|
timeout ||= @timeout
|
149
156
|
start_time = ::OpenTelemetry::Common::Utilities.timeout_timestamp
|
150
|
-
|
157
|
+
|
151
158
|
around_request do
|
152
159
|
remaining_timeout = ::OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time)
|
153
160
|
return FAILURE if remaining_timeout.zero?
|
154
|
-
|
161
|
+
|
155
162
|
@http.open_timeout = remaining_timeout
|
156
163
|
@http.read_timeout = remaining_timeout
|
157
|
-
@http.write_timeout = remaining_timeout
|
164
|
+
@http.write_timeout = remaining_timeout
|
158
165
|
@http.start unless @http.started?
|
159
|
-
response =
|
160
|
-
|
166
|
+
response = @http.request(request)
|
167
|
+
|
161
168
|
case response
|
162
169
|
when Net::HTTPOK
|
163
170
|
response.body # Read and discard body
|
164
171
|
SUCCESS
|
165
172
|
when Net::HTTPServiceUnavailable, Net::HTTPTooManyRequests
|
166
173
|
response.body # Read and discard body
|
167
|
-
|
174
|
+
handle_http_error(response)
|
175
|
+
redo if backoff?(retry_after: response['Retry-After'], retry_count: retry_count += 1)
|
168
176
|
FAILURE
|
169
177
|
when Net::HTTPRequestTimeOut, Net::HTTPGatewayTimeOut, Net::HTTPBadGateway
|
170
178
|
response.body # Read and discard body
|
171
|
-
|
179
|
+
handle_http_error(response)
|
180
|
+
redo if backoff?(retry_count: retry_count += 1)
|
172
181
|
FAILURE
|
173
182
|
when Net::HTTPNotFound
|
174
|
-
|
183
|
+
handle_http_error(response)
|
175
184
|
FAILURE
|
176
185
|
when Net::HTTPBadRequest, Net::HTTPClientError, Net::HTTPServerError
|
177
186
|
log_status(response.body)
|
@@ -179,28 +188,35 @@ module ScoutApm
|
|
179
188
|
when Net::HTTPRedirection
|
180
189
|
@http.finish
|
181
190
|
handle_redirect(response['location'])
|
182
|
-
redo if backoff?(retry_after: 0, retry_count: retry_count += 1
|
191
|
+
redo if backoff?(retry_after: 0, retry_count: retry_count += 1)
|
183
192
|
else
|
184
193
|
@http.finish
|
194
|
+
handle_http_error(response)
|
185
195
|
FAILURE
|
186
196
|
end
|
187
|
-
rescue Net::OpenTimeout, Net::ReadTimeout
|
188
|
-
|
197
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
198
|
+
OpenTelemetry.handle_error(exception: e)
|
199
|
+
retry if backoff?(retry_count: retry_count += 1)
|
189
200
|
return FAILURE
|
190
|
-
rescue OpenSSL::SSL::SSLError
|
191
|
-
|
201
|
+
rescue OpenSSL::SSL::SSLError => e
|
202
|
+
OpenTelemetry.handle_error(exception: e)
|
203
|
+
retry if backoff?(retry_count: retry_count += 1)
|
192
204
|
return FAILURE
|
193
|
-
rescue SocketError
|
194
|
-
|
205
|
+
rescue SocketError => e
|
206
|
+
OpenTelemetry.handle_error(exception: e)
|
207
|
+
retry if backoff?(retry_count: retry_count += 1)
|
195
208
|
return FAILURE
|
196
209
|
rescue SystemCallError => e
|
197
|
-
retry if backoff?(retry_count: retry_count += 1
|
210
|
+
retry if backoff?(retry_count: retry_count += 1)
|
211
|
+
OpenTelemetry.handle_error(exception: e)
|
198
212
|
return FAILURE
|
199
|
-
rescue EOFError
|
200
|
-
|
213
|
+
rescue EOFError => e
|
214
|
+
OpenTelemetry.handle_error(exception: e)
|
215
|
+
retry if backoff?(retry_count: retry_count += 1)
|
201
216
|
return FAILURE
|
202
|
-
rescue Zlib::DataError
|
203
|
-
|
217
|
+
rescue Zlib::DataError => e
|
218
|
+
OpenTelemetry.handle_error(exception: e)
|
219
|
+
retry if backoff?(retry_count: retry_count += 1)
|
204
220
|
return FAILURE
|
205
221
|
rescue StandardError => e
|
206
222
|
OpenTelemetry.handle_error(exception: e, message: 'unexpected error in OTLP::Exporter#send_bytes')
|
@@ -210,37 +226,27 @@ module ScoutApm
|
|
210
226
|
# Reset timeouts to defaults for the next call.
|
211
227
|
@http.open_timeout = @timeout
|
212
228
|
@http.read_timeout = @timeout
|
213
|
-
@http.write_timeout = @timeout
|
229
|
+
@http.write_timeout = @timeout
|
214
230
|
end
|
215
|
-
|
231
|
+
|
216
232
|
def handle_redirect(location)
|
217
233
|
# TODO: figure out destination and reinitialize @http and @path
|
218
234
|
end
|
219
|
-
|
235
|
+
|
220
236
|
def log_status(body)
|
221
237
|
status = Google::Rpc::Status.decode(body)
|
222
238
|
details = status.details.map do |detail|
|
223
239
|
klass_or_nil = ::Google::Protobuf::DescriptorPool.generated_pool.lookup(detail.type_name).msgclass
|
224
240
|
detail.unpack(klass_or_nil) if klass_or_nil
|
225
241
|
end.compact
|
226
|
-
OpenTelemetry.handle_error(message: "OTLP exporter received rpc.Status{message=#{status.message}, details=#{details}}")
|
242
|
+
OpenTelemetry.handle_error(message: "OTLP logs exporter received rpc.Status{message=#{status.message}, details=#{details}}")
|
227
243
|
rescue StandardError => e
|
228
244
|
OpenTelemetry.handle_error(exception: e, message: 'unexpected error decoding rpc.Status in OTLP::Exporter#log_status')
|
229
245
|
end
|
230
|
-
|
231
|
-
def
|
232
|
-
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
233
|
-
begin
|
234
|
-
yield
|
235
|
-
ensure
|
236
|
-
stop = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
237
|
-
1000.0 * (stop - start)
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
def backoff?(retry_count:, reason:, retry_after: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
246
|
+
|
247
|
+
def backoff?(retry_count:, retry_after: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
242
248
|
return false if retry_count > RETRY_COUNT
|
243
|
-
|
249
|
+
|
244
250
|
sleep_interval = nil
|
245
251
|
unless retry_after.nil?
|
246
252
|
sleep_interval =
|
@@ -258,11 +264,11 @@ module ScoutApm
|
|
258
264
|
sleep_interval = nil unless sleep_interval&.positive?
|
259
265
|
end
|
260
266
|
sleep_interval ||= rand(2**retry_count)
|
261
|
-
|
267
|
+
|
262
268
|
sleep(sleep_interval)
|
263
269
|
true
|
264
270
|
end
|
265
|
-
|
271
|
+
|
266
272
|
def encode(log_record_data) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
267
273
|
Opentelemetry::Proto::Collector::Logs::V1::ExportLogsServiceRequest.encode(
|
268
274
|
Opentelemetry::Proto::Collector::Logs::V1::ExportLogsServiceRequest.new(
|
@@ -292,7 +298,7 @@ module ScoutApm
|
|
292
298
|
OpenTelemetry.handle_error(exception: e, message: 'unexpected error in OTLP::Exporter#encode')
|
293
299
|
nil
|
294
300
|
end
|
295
|
-
|
301
|
+
|
296
302
|
def as_otlp_log_record(log_record_data)
|
297
303
|
Opentelemetry::Proto::Logs::V1::LogRecord.new(
|
298
304
|
time_unix_nano: log_record_data.timestamp,
|
@@ -307,7 +313,7 @@ module ScoutApm
|
|
307
313
|
span_id: log_record_data.span_id
|
308
314
|
)
|
309
315
|
end
|
310
|
-
|
316
|
+
|
311
317
|
def as_otlp_key_value(key, value)
|
312
318
|
Opentelemetry::Proto::Common::V1::KeyValue.new(key: key, value: as_otlp_any_value(value))
|
313
319
|
rescue Encoding::UndefinedConversionError => e
|
@@ -315,7 +321,7 @@ module ScoutApm
|
|
315
321
|
OpenTelemetry.handle_error(exception: e, message: "encoding error for key #{key} and value #{encoded_value}")
|
316
322
|
Opentelemetry::Proto::Common::V1::KeyValue.new(key: key, value: as_otlp_any_value('Encoding Error'))
|
317
323
|
end
|
318
|
-
|
324
|
+
|
319
325
|
def as_otlp_any_value(value)
|
320
326
|
result = Opentelemetry::Proto::Common::V1::AnyValue.new
|
321
327
|
case value
|
@@ -333,6 +339,22 @@ module ScoutApm
|
|
333
339
|
end
|
334
340
|
result
|
335
341
|
end
|
342
|
+
|
343
|
+
def prepare_headers(config_headers)
|
344
|
+
headers = case config_headers
|
345
|
+
when String then parse_headers(config_headers)
|
346
|
+
when Hash then config_headers.dup
|
347
|
+
else
|
348
|
+
raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS
|
349
|
+
end
|
350
|
+
|
351
|
+
headers['User-Agent'] = "#{headers.fetch('User-Agent', '')} #{DEFAULT_USER_AGENT}".strip
|
352
|
+
|
353
|
+
headers
|
354
|
+
end
|
355
|
+
|
356
|
+
# NOTE: This has been removed in newer versions of the OpenTelemetry Ruby Logs SDK,
|
357
|
+
# but we need it for backward compatibility with the protobuf definitions.
|
336
358
|
|
337
359
|
# TODO: maybe don't translate the severity number, but translate the severity text into
|
338
360
|
# the number if the number is nil? Poss. change to allow for adding your own
|
@@ -347,24 +369,11 @@ module ScoutApm
|
|
347
369
|
when 5 then Opentelemetry::Proto::Logs::V1::SeverityNumber::SEVERITY_NUMBER_UNSPECIFIED
|
348
370
|
end
|
349
371
|
end
|
350
|
-
|
351
|
-
def prepare_headers(config_headers)
|
352
|
-
headers = case config_headers
|
353
|
-
when String then parse_headers(config_headers)
|
354
|
-
when Hash then config_headers.dup
|
355
|
-
else
|
356
|
-
raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS
|
357
|
-
end
|
358
|
-
|
359
|
-
headers['User-Agent'] = "#{headers.fetch('User-Agent', '')} #{DEFAULT_USER_AGENT}".strip
|
360
|
-
|
361
|
-
headers
|
362
|
-
end
|
363
|
-
|
372
|
+
|
364
373
|
def parse_headers(raw)
|
365
374
|
entries = raw.split(',')
|
366
375
|
raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS if entries.empty?
|
367
|
-
|
376
|
+
|
368
377
|
entries.each_with_object({}) do |entry, headers|
|
369
378
|
k, v = entry.split('=', 2).map(&CGI.method(:unescape))
|
370
379
|
begin
|
@@ -376,7 +385,7 @@ module ScoutApm
|
|
376
385
|
raise e, ERROR_MESSAGE_INVALID_HEADERS
|
377
386
|
end
|
378
387
|
raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS if k.empty? || v.empty?
|
379
|
-
|
388
|
+
|
380
389
|
headers[k] = v
|
381
390
|
end
|
382
391
|
end
|
data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/batch_log_record_processor.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
# Copyright The OpenTelemetry Authors
|
4
4
|
#
|
5
5
|
# SPDX-License-Identifier: Apache-2.0
|
6
|
-
|
7
6
|
module ScoutApm
|
8
7
|
module Logging
|
9
8
|
module Loggers
|
@@ -41,9 +40,8 @@ module ScoutApm
|
|
41
40
|
exporter_timeout: Float(ENV.fetch('OTEL_BLRP_EXPORT_TIMEOUT', 30_000)),
|
42
41
|
schedule_delay: Float(ENV.fetch('OTEL_BLRP_SCHEDULE_DELAY', 1000)),
|
43
42
|
max_queue_size: Integer(ENV.fetch('OTEL_BLRP_MAX_QUEUE_SIZE', 2048)),
|
44
|
-
max_export_batch_size: Integer(ENV.fetch('OTEL_BLRP_MAX_EXPORT_BATCH_SIZE',
|
43
|
+
max_export_batch_size: Integer(ENV.fetch('OTEL_BLRP_MAX_EXPORT_BATCH_SIZE', 512)),
|
45
44
|
start_thread_on_boot: String(ENV['OTEL_RUBY_BLRP_START_THREAD_ON_BOOT']) !~ /false/i)
|
46
|
-
|
47
45
|
unless max_export_batch_size <= max_queue_size
|
48
46
|
raise ArgumentError,
|
49
47
|
'max_export_batch_size much be less than or equal to max_queue_size'
|
@@ -12,6 +12,10 @@ module ScoutApm
|
|
12
12
|
module Logs
|
13
13
|
# Implementation of OpenTelemetry::Logs::LogRecord that records log events.
|
14
14
|
class LogRecord < OpenTelemetry::Logs::LogRecord
|
15
|
+
EMPTY_ATTRIBUTES = {}.freeze
|
16
|
+
|
17
|
+
private_constant :EMPTY_ATTRIBUTES
|
18
|
+
|
15
19
|
attr_accessor :timestamp,
|
16
20
|
:observed_timestamp,
|
17
21
|
:severity_text,
|
@@ -52,6 +56,8 @@ module ScoutApm
|
|
52
56
|
# source of the log, desrived from the LoggerProvider.
|
53
57
|
# @param [optional OpenTelemetry::SDK::InstrumentationScope] instrumentation_scope
|
54
58
|
# The instrumentation scope, derived from the emitting Logger
|
59
|
+
# @param [optional] OpenTelemetry::SDK::LogRecordLimits] log_record_limits
|
60
|
+
# Attribute limits
|
55
61
|
#
|
56
62
|
#
|
57
63
|
# @return [LogRecord]
|
@@ -66,7 +72,8 @@ module ScoutApm
|
|
66
72
|
span_id: nil,
|
67
73
|
trace_flags: nil,
|
68
74
|
resource: nil,
|
69
|
-
instrumentation_scope: nil
|
75
|
+
instrumentation_scope: nil,
|
76
|
+
log_record_limits: nil
|
70
77
|
)
|
71
78
|
@timestamp = timestamp
|
72
79
|
@observed_timestamp = observed_timestamp || timestamp || Time.now
|
@@ -79,7 +86,10 @@ module ScoutApm
|
|
79
86
|
@trace_flags = trace_flags
|
80
87
|
@resource = resource
|
81
88
|
@instrumentation_scope = instrumentation_scope
|
89
|
+
@log_record_limits = log_record_limits || LogRecordLimits::DEFAULT
|
82
90
|
@total_recorded_attributes = @attributes&.size || 0
|
91
|
+
|
92
|
+
trim_attributes(@attributes)
|
83
93
|
end
|
84
94
|
|
85
95
|
def to_log_record_data
|
@@ -106,6 +116,51 @@ module ScoutApm
|
|
106
116
|
|
107
117
|
(timestamp.to_r * 10**9).to_i
|
108
118
|
end
|
119
|
+
|
120
|
+
def trim_attributes(attributes)
|
121
|
+
return if attributes.nil?
|
122
|
+
|
123
|
+
# truncate total attributes
|
124
|
+
truncate_attributes(attributes, @log_record_limits.attribute_count_limit)
|
125
|
+
|
126
|
+
# truncate attribute values
|
127
|
+
truncate_attribute_values(attributes, @log_record_limits.attribute_length_limit)
|
128
|
+
|
129
|
+
# validate attributes
|
130
|
+
validate_attributes(attributes)
|
131
|
+
|
132
|
+
nil
|
133
|
+
end
|
134
|
+
|
135
|
+
def truncate_attributes(attributes, attribute_limit)
|
136
|
+
excess = attributes.size - attribute_limit
|
137
|
+
excess.times { attributes.shift } if excess.positive?
|
138
|
+
end
|
139
|
+
|
140
|
+
def validate_attributes(attrs)
|
141
|
+
# Similar to Internal.valid_attributes?, but with different messages
|
142
|
+
# Future refactor opportunity: https://github.com/open-telemetry/opentelemetry-ruby/issues/1739
|
143
|
+
attrs.keep_if do |k, v|
|
144
|
+
if !::OpenTelemetry::SDK::Internal.valid_key?(k)
|
145
|
+
OpenTelemetry.handle_error(message: "invalid log record attribute key type #{k.class}, #{k} on record: '#{body}'")
|
146
|
+
return false
|
147
|
+
elsif !::OpenTelemetry::SDK::Internal.valid_value?(v)
|
148
|
+
OpenTelemetry.handle_error(message: "invalid log record attribute value type #{v.class} for key '#{k}' on record: '#{body}'")
|
149
|
+
return false
|
150
|
+
end
|
151
|
+
|
152
|
+
true
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def truncate_attribute_values(attributes, attribute_length_limit)
|
157
|
+
return EMPTY_ATTRIBUTES if attributes.nil?
|
158
|
+
return attributes if attribute_length_limit.nil?
|
159
|
+
|
160
|
+
attributes.transform_values! { |value| ::OpenTelemetry::Common::Utilities.truncate_attribute_value(value, attribute_length_limit) }
|
161
|
+
|
162
|
+
attributes
|
163
|
+
end
|
109
164
|
end
|
110
165
|
end
|
111
166
|
end
|
@@ -16,7 +16,7 @@ module ScoutApm
|
|
16
16
|
:severity_text, # optional String
|
17
17
|
:severity_number, # optional Integer
|
18
18
|
:body, # optional String, Numeric, Boolean, Array<String, Numeric, Boolean>, Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}
|
19
|
-
:attributes,
|
19
|
+
:attributes, # optional Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}
|
20
20
|
:trace_id, # optional String (16-byte binary)
|
21
21
|
:span_id, # optional String (8-byte binary)
|
22
22
|
:trace_flags, # optional Integer (8-bit byte of bit flags)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright The OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module ScoutApm
|
8
|
+
module Logging
|
9
|
+
module Loggers
|
10
|
+
module OpenTelemetry
|
11
|
+
module SDK
|
12
|
+
module Logs
|
13
|
+
# Class that holds log record attribute limit parameters.
|
14
|
+
class LogRecordLimits
|
15
|
+
# The global default max number of attributes per {LogRecord}.
|
16
|
+
attr_reader :attribute_count_limit
|
17
|
+
|
18
|
+
# The global default max length of attribute value per {LogRecord}.
|
19
|
+
attr_reader :attribute_length_limit
|
20
|
+
|
21
|
+
# Returns a {LogRecordLimits} with the desired values.
|
22
|
+
#
|
23
|
+
# @return [LogRecordLimits] with the desired values.
|
24
|
+
# @raise [ArgumentError] if any of the max numbers are not positive.
|
25
|
+
def initialize(attribute_count_limit: Integer(::OpenTelemetry::Common::Utilities.config_opt(
|
26
|
+
'OTEL_LOG_RECORD_ATTRIBUTE_COUNT_LIMIT',
|
27
|
+
'OTEL_ATTRIBUTE_COUNT_LIMIT',
|
28
|
+
default: 128
|
29
|
+
)),
|
30
|
+
attribute_length_limit: ::OpenTelemetry::Common::Utilities.config_opt(
|
31
|
+
'OTEL_LOG_RECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT',
|
32
|
+
'OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT'
|
33
|
+
))
|
34
|
+
raise ArgumentError, 'attribute_count_limit must be positive' unless attribute_count_limit.positive?
|
35
|
+
raise ArgumentError, 'attribute_length_limit must not be less than 32' unless attribute_length_limit.nil? || Integer(attribute_length_limit) >= 32
|
36
|
+
|
37
|
+
@attribute_count_limit = attribute_count_limit
|
38
|
+
@attribute_length_limit = attribute_length_limit.nil? ? nil : Integer(attribute_length_limit)
|
39
|
+
end
|
40
|
+
|
41
|
+
# The default {LogRecordLimits}.
|
42
|
+
DEFAULT = new
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -3,7 +3,6 @@
|
|
3
3
|
# Copyright The OpenTelemetry Authors
|
4
4
|
#
|
5
5
|
# SPDX-License-Identifier: Apache-2.0
|
6
|
-
|
7
6
|
module ScoutApm
|
8
7
|
module Logging
|
9
8
|
module Loggers
|
@@ -29,7 +28,7 @@ module ScoutApm
|
|
29
28
|
# the process after an invocation, but before the `Processor` exports
|
30
29
|
# the completed spans.
|
31
30
|
#
|
32
|
-
# @param [Numeric] timeout An optional timeout in seconds.
|
31
|
+
# @param [optional Numeric] timeout An optional timeout in seconds.
|
33
32
|
# @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
|
34
33
|
# a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
|
35
34
|
def force_flush(timeout: nil)
|
@@ -38,7 +37,7 @@ module ScoutApm
|
|
38
37
|
|
39
38
|
# Called when {LoggerProvider#shutdown} is called.
|
40
39
|
#
|
41
|
-
# @param [Numeric] timeout An optional timeout in seconds.
|
40
|
+
# @param [optional Numeric] timeout An optional timeout in seconds.
|
42
41
|
# @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
|
43
42
|
# a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
|
44
43
|
def shutdown(timeout: nil)
|
@@ -37,6 +37,8 @@ module ScoutApm
|
|
37
37
|
# @param [optional OpenTelemetry::Trace::SpanContext] span_context The
|
38
38
|
# OpenTelemetry::Trace::SpanContext to associate with the
|
39
39
|
# {LogRecord}.
|
40
|
+
# @param [optional String] severity_text Original string representation of
|
41
|
+
# the severity as it is known at the source. Also known as log level.
|
40
42
|
# @param severity_number [optional Integer] Numerical value of the
|
41
43
|
# severity. Smaller numerical values correspond to less severe events
|
42
44
|
# (such as debug events), larger numerical values correspond to more
|
@@ -72,6 +74,8 @@ module ScoutApm
|
|
72
74
|
span_id: nil,
|
73
75
|
trace_flags: nil,
|
74
76
|
context: ::OpenTelemetry::Context.current)
|
77
|
+
current_span = ::OpenTelemetry::Trace.current_span(context)
|
78
|
+
span_context = current_span.context unless current_span == ::OpenTelemetry::Trace::Span::INVALID
|
75
79
|
|
76
80
|
@logger_provider.on_emit(timestamp: timestamp,
|
77
81
|
observed_timestamp: observed_timestamp,
|
@@ -79,9 +83,9 @@ module ScoutApm
|
|
79
83
|
severity_number: severity_number,
|
80
84
|
body: body,
|
81
85
|
attributes: attributes,
|
82
|
-
trace_id:
|
83
|
-
span_id:
|
84
|
-
trace_flags:
|
86
|
+
trace_id: trace_id || span_context&.trace_id,
|
87
|
+
span_id: span_id || span_context&.span_id,
|
88
|
+
trace_flags: trace_flags || span_context&.trace_flags,
|
85
89
|
instrumentation_scope: @instrumentation_scope,
|
86
90
|
context: context)
|
87
91
|
end
|
@@ -12,6 +12,9 @@ module ScoutApm
|
|
12
12
|
module Logs
|
13
13
|
# The SDK implementation of OpenTelemetry::Logs::LoggerProvider.
|
14
14
|
class LoggerProvider < OpenTelemetry::Logs::LoggerProvider
|
15
|
+
Key = Struct.new(:name, :version)
|
16
|
+
private_constant(:Key)
|
17
|
+
|
15
18
|
UNEXPECTED_ERROR_MESSAGE = 'unexpected error in ' \
|
16
19
|
'OpenTelemetry::SDK::Logs::LoggerProvider#%s'
|
17
20
|
|
@@ -21,13 +24,18 @@ module ScoutApm
|
|
21
24
|
#
|
22
25
|
# @param [optional Resource] resource The resource to associate with
|
23
26
|
# new LogRecords created by {Logger}s created by this LoggerProvider.
|
27
|
+
# @param [optional LogRecordLimits] log_record_limits The limits for
|
28
|
+
# attributes count and attribute length for LogRecords.
|
24
29
|
#
|
25
30
|
# @return [OpenTelemetry::SDK::Logs::LoggerProvider]
|
26
|
-
def initialize(resource: OpenTelemetry::SDK::Resources::Resource.create)
|
31
|
+
def initialize(resource: OpenTelemetry::SDK::Resources::Resource.create, log_record_limits: LogRecordLimits::DEFAULT)
|
27
32
|
@log_record_processors = []
|
33
|
+
@log_record_limits = log_record_limits
|
28
34
|
@mutex = Mutex.new
|
29
35
|
@resource = resource
|
30
36
|
@stopped = false
|
37
|
+
@registry = {}
|
38
|
+
@registry_mutex = Mutex.new
|
31
39
|
end
|
32
40
|
|
33
41
|
# Returns an {OpenTelemetry::SDK::Logs::Logger} instance.
|
@@ -44,7 +52,9 @@ module ScoutApm
|
|
44
52
|
"invalid name. Name provided: #{name.inspect}")
|
45
53
|
end
|
46
54
|
|
47
|
-
|
55
|
+
@registry_mutex.synchronize do
|
56
|
+
@registry[Key.new(name, version)] ||= Logger.new(name, version, self)
|
57
|
+
end
|
48
58
|
end
|
49
59
|
|
50
60
|
# Adds a new log record processor to this LoggerProvider's
|
@@ -134,6 +144,7 @@ module ScoutApm
|
|
134
144
|
trace_flags: nil,
|
135
145
|
instrumentation_scope: nil,
|
136
146
|
context: nil)
|
147
|
+
return if @stopped
|
137
148
|
|
138
149
|
log_record = LogRecord.new(timestamp: timestamp,
|
139
150
|
observed_timestamp: observed_timestamp,
|
@@ -145,7 +156,8 @@ module ScoutApm
|
|
145
156
|
span_id: span_id,
|
146
157
|
trace_flags: trace_flags,
|
147
158
|
resource: @resource,
|
148
|
-
instrumentation_scope: instrumentation_scope
|
159
|
+
instrumentation_scope: instrumentation_scope,
|
160
|
+
log_record_limits: @log_record_limits)
|
149
161
|
|
150
162
|
@log_record_processors.each { |processor| processor.on_emit(log_record, context) }
|
151
163
|
end
|
data/scout_apm_logging.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.authors = 'Scout APM'
|
8
8
|
s.email = ['support@scoutapp.com']
|
9
9
|
s.homepage = 'https://github.com/scoutapp/scout_apm_ruby_logging'
|
10
|
-
s.summary = 'Ruby
|
10
|
+
s.summary = 'Managed log monitoring for Ruby applications.'
|
11
11
|
s.description = 'Sets up log monitoring for Scout APM Ruby clients.'
|
12
12
|
s.license = 'MIT'
|
13
13
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scout_apm_logging
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scout APM
|
@@ -208,6 +208,7 @@ files:
|
|
208
208
|
- lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/log_record_exporter.rb
|
209
209
|
- lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record.rb
|
210
210
|
- lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_data.rb
|
211
|
+
- lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_limits.rb
|
211
212
|
- lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_processor.rb
|
212
213
|
- lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger.rb
|
213
214
|
- lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger_provider.rb
|
@@ -251,7 +252,7 @@ requirements: []
|
|
251
252
|
rubygems_version: 3.3.26
|
252
253
|
signing_key:
|
253
254
|
specification_version: 4
|
254
|
-
summary: Ruby
|
255
|
+
summary: Managed log monitoring for Ruby applications.
|
255
256
|
test_files:
|
256
257
|
- spec/data/config_test_1.yml
|
257
258
|
- spec/data/mock_config.yml
|