scout_apm_logging 0.0.13 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +3 -0
- data/NOTICE +4 -0
- data/lib/scout_apm/logging/config.rb +5 -131
- data/lib/scout_apm/logging/loggers/capture.rb +3 -2
- data/lib/scout_apm/logging/loggers/formatter.rb +21 -2
- data/lib/scout_apm/logging/loggers/opentelemetry/LICENSE +201 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/NOTICE +9 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/log_record.rb +18 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/logger.rb +64 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/logger_provider.rb +31 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/severity_number.rb +43 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/version.rb +18 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/api/logs.rb +28 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/exporter/exporter/otlp/logs_exporter.rb +389 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/exporter/exporter/otlp/version.rb +20 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/collector/logs/v1/logs_service_pb.rb +43 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/common/v1/common_pb.rb +58 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/logs/v1/logs_pb.rb +91 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/resource/v1/resource_pb.rb +33 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/opentelemetry.rb +62 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/batch_log_record_processor.rb +225 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/log_record_exporter.rb +64 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export.rb +34 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record.rb +115 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_data.rb +31 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_processor.rb +53 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger.rb +94 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger_provider.rb +158 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/version.rb +20 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs.rb +28 -0
- data/lib/scout_apm/logging/utils.rb +0 -69
- data/lib/scout_apm/logging/version.rb +1 -1
- data/lib/scout_apm_logging.rb +2 -11
- data/scout_apm_logging.gemspec +7 -0
- data/spec/data/config_test_1.yml +0 -1
- data/spec/data/mock_config.yml +0 -3
- data/spec/integration/rails/lifecycle_spec.rb +57 -23
- data/spec/spec_helper.rb +0 -12
- data/spec/unit/config_spec.rb +0 -12
- data/spec/unit/loggers/capture_spec.rb +0 -6
- metadata +126 -39
- data/bin/scout_apm_logging_monitor +0 -6
- data/lib/scout_apm/logging/monitor/_rails.rb +0 -22
- data/lib/scout_apm/logging/monitor/collector/checksum.rb +0 -51
- data/lib/scout_apm/logging/monitor/collector/configuration.rb +0 -150
- data/lib/scout_apm/logging/monitor/collector/downloader.rb +0 -78
- data/lib/scout_apm/logging/monitor/collector/extractor.rb +0 -37
- data/lib/scout_apm/logging/monitor/collector/manager.rb +0 -57
- data/lib/scout_apm/logging/monitor/monitor.rb +0 -216
- data/lib/scout_apm/logging/monitor_manager/manager.rb +0 -162
- data/lib/scout_apm/logging/state.rb +0 -69
- data/spec/data/empty_logs_config.yml +0 -0
- data/spec/data/logs_config.yml +0 -3
- data/spec/data/state_file.json +0 -3
- data/spec/integration/loggers/capture_spec.rb +0 -68
- data/spec/integration/monitor/collector/downloader/will_verify_checksum.rb +0 -49
- data/spec/integration/monitor/collector_healthcheck_spec.rb +0 -29
- data/spec/integration/monitor/continuous_state_collector_spec.rb +0 -31
- data/spec/integration/monitor/previous_collector_setup_spec.rb +0 -45
- data/spec/integration/monitor_manager/disable_agent_spec.rb +0 -30
- data/spec/integration/monitor_manager/monitor_pid_file_spec.rb +0 -38
- data/spec/integration/monitor_manager/single_monitor_spec.rb +0 -53
- data/spec/unit/monitor/collector/configuration_spec.rb +0 -64
- data/spec/unit/state_spec.rb +0 -20
- data/tooling/checksums.rb +0 -106
@@ -0,0 +1,18 @@
|
|
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 Logs
|
12
|
+
## Current OpenTelemetry logs version
|
13
|
+
VERSION = '0.1.0'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright The OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
require_relative 'logs/log_record'
|
8
|
+
require_relative 'logs/logger'
|
9
|
+
require_relative 'logs/logger_provider'
|
10
|
+
require_relative 'logs/severity_number'
|
11
|
+
|
12
|
+
module ScoutApm
|
13
|
+
module Logging
|
14
|
+
module Loggers
|
15
|
+
module OpenTelemetry
|
16
|
+
# The Logs API records a timestamped record with metadata.
|
17
|
+
# In OpenTelemetry, any data that is not part of a distributed trace or a
|
18
|
+
# metric is a log. For example, events are a specific type of log.
|
19
|
+
#
|
20
|
+
# This API is provided for logging library authors to build log
|
21
|
+
# appenders/bridges. It should NOT be used directly by application
|
22
|
+
# developers.
|
23
|
+
module Logs
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,389 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright The OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
require 'opentelemetry/common'
|
8
|
+
require 'opentelemetry/sdk'
|
9
|
+
require 'net/http'
|
10
|
+
require 'zlib'
|
11
|
+
|
12
|
+
require 'google/rpc/status_pb'
|
13
|
+
|
14
|
+
require_relative '../../proto/common/v1/common_pb'
|
15
|
+
require_relative '../../proto/resource/v1/resource_pb'
|
16
|
+
require_relative '../../proto/logs/v1/logs_pb'
|
17
|
+
require_relative '../../proto/collector/logs/v1/logs_service_pb'
|
18
|
+
|
19
|
+
module ScoutApm
|
20
|
+
module Logging
|
21
|
+
module Loggers
|
22
|
+
module OpenTelemetry
|
23
|
+
module Exporter
|
24
|
+
module OTLP
|
25
|
+
# An OpenTelemetry log exporter that sends log records over HTTP as Protobuf encoded OTLP ExportLogsServiceRequests.
|
26
|
+
class LogsExporter # rubocop:disable Metrics/ClassLength
|
27
|
+
SUCCESS = OpenTelemetry::SDK::Logs::Export::SUCCESS
|
28
|
+
FAILURE = OpenTelemetry::SDK::Logs::Export::FAILURE
|
29
|
+
private_constant(:SUCCESS, :FAILURE)
|
30
|
+
|
31
|
+
# Default timeouts in seconds.
|
32
|
+
KEEP_ALIVE_TIMEOUT = 30
|
33
|
+
RETRY_COUNT = 5
|
34
|
+
WRITE_TIMEOUT_SUPPORTED = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6')
|
35
|
+
private_constant(:KEEP_ALIVE_TIMEOUT, :RETRY_COUNT, :WRITE_TIMEOUT_SUPPORTED)
|
36
|
+
|
37
|
+
ERROR_MESSAGE_INVALID_HEADERS = 'headers must be a String with comma-separated URL Encoded UTF-8 k=v pairs or a Hash'
|
38
|
+
private_constant(:ERROR_MESSAGE_INVALID_HEADERS)
|
39
|
+
|
40
|
+
DEFAULT_USER_AGENT = "OTel-OTLP-Exporter-Ruby/#{OpenTelemetry::Exporter::OTLP::VERSION} Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM}; #{RUBY_ENGINE}/#{RUBY_ENGINE_VERSION})".freeze
|
41
|
+
|
42
|
+
def self.ssl_verify_mode
|
43
|
+
if ENV.key?('OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_PEER')
|
44
|
+
OpenSSL::SSL::VERIFY_PEER
|
45
|
+
elsif ENV.key?('OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_NONE')
|
46
|
+
OpenSSL::SSL::VERIFY_NONE
|
47
|
+
else
|
48
|
+
OpenSSL::SSL::VERIFY_PEER
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(endpoint: ::OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_LOGS_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT', default: 'http://localhost:4318/v1/logs'),
|
53
|
+
certificate_file: ::OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE', 'OTEL_EXPORTER_OTLP_CERTIFICATE'),
|
54
|
+
ssl_verify_mode: LogsExporter.ssl_verify_mode,
|
55
|
+
headers: ::OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_LOGS_HEADERS', 'OTEL_EXPORTER_OTLP_HEADERS', default: {}),
|
56
|
+
compression: ::OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_LOGS_COMPRESSION', 'OTEL_EXPORTER_OTLP_COMPRESSION', default: 'gzip'),
|
57
|
+
timeout: ::OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_LOGS_TIMEOUT', 'OTEL_EXPORTER_OTLP_TIMEOUT', default: 10))
|
58
|
+
raise ArgumentError, "invalid url for OTLP::Exporter #{endpoint}" unless ::OpenTelemetry::Common::Utilities.valid_url?(endpoint)
|
59
|
+
raise ArgumentError, "unsupported compression key #{compression}" unless compression.nil? || %w[gzip none].include?(compression)
|
60
|
+
|
61
|
+
@uri = if endpoint == ENV['OTEL_EXPORTER_OTLP_ENDPOINT']
|
62
|
+
URI.join(endpoint, 'v1/logs')
|
63
|
+
else
|
64
|
+
URI(endpoint)
|
65
|
+
end
|
66
|
+
|
67
|
+
@http = http_connection(@uri, ssl_verify_mode, certificate_file)
|
68
|
+
|
69
|
+
@path = @uri.path
|
70
|
+
@headers = prepare_headers(headers)
|
71
|
+
@timeout = timeout.to_f
|
72
|
+
@compression = compression
|
73
|
+
@shutdown = false
|
74
|
+
end
|
75
|
+
|
76
|
+
# Called to export sampled {OpenTelemetry::SDK::Logs::LogRecordData} structs.
|
77
|
+
#
|
78
|
+
# @param [Enumerable<OpenTelemetry::SDK::Logs::LogRecordData>] log_record_data the
|
79
|
+
# list of recorded {OpenTelemetry::SDK::Logs::LogRecordData} structs to be
|
80
|
+
# exported.
|
81
|
+
# @param [optional Numeric] timeout An optional timeout in seconds.
|
82
|
+
# @return [Integer] the result of the export.
|
83
|
+
def export(log_record_data, timeout: nil)
|
84
|
+
OpenTelemetry.logger.error('Logs Exporter tried to export, but it has already shut down') if @shutdown
|
85
|
+
return FAILURE if @shutdown
|
86
|
+
|
87
|
+
send_bytes(encode(log_record_data), timeout: timeout)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Called when {OpenTelemetry::SDK::Logs::LoggerProvider#force_flush} is called, if
|
91
|
+
# this exporter is registered to a {OpenTelemetry::SDK::Logs::LoggerProvider}
|
92
|
+
# object.
|
93
|
+
#
|
94
|
+
# @param [optional Numeric] timeout An optional timeout in seconds.
|
95
|
+
def force_flush(timeout: nil)
|
96
|
+
SUCCESS
|
97
|
+
end
|
98
|
+
|
99
|
+
# Called when {OpenTelemetry::SDK::Logs::LoggerProvider#shutdown} is called, if
|
100
|
+
# this exporter is registered to a {OpenTelemetry::SDK::Logs::LoggerProvider}
|
101
|
+
# object.
|
102
|
+
#
|
103
|
+
# @param [optional Numeric] timeout An optional timeout in seconds.
|
104
|
+
def shutdown(timeout: nil)
|
105
|
+
@shutdown = true
|
106
|
+
@http.finish if @http.started?
|
107
|
+
SUCCESS
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def http_connection(uri, ssl_verify_mode, certificate_file)
|
113
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
114
|
+
http.use_ssl = uri.scheme == 'https'
|
115
|
+
http.verify_mode = ssl_verify_mode
|
116
|
+
http.ca_file = certificate_file unless certificate_file.nil?
|
117
|
+
http.keep_alive_timeout = KEEP_ALIVE_TIMEOUT
|
118
|
+
http
|
119
|
+
end
|
120
|
+
|
121
|
+
# The around_request is a private method that provides an extension
|
122
|
+
# point for the exporters network calls. The default behaviour
|
123
|
+
# is to not record these operations.
|
124
|
+
#
|
125
|
+
# An example use case would be to prepend a patch, or extend this class
|
126
|
+
# and override this method's behaviour to explicitly record the HTTP request.
|
127
|
+
# This would allow you to create log records for your export pipeline.
|
128
|
+
def around_request
|
129
|
+
::OpenTelemetry::Common::Utilities.untraced { yield } # rubocop:disable Style/ExplicitBlockArgument
|
130
|
+
end
|
131
|
+
|
132
|
+
def send_bytes(bytes, timeout:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
133
|
+
return FAILURE if bytes.nil?
|
134
|
+
|
135
|
+
request = Net::HTTP::Post.new(@path)
|
136
|
+
if @compression == 'gzip'
|
137
|
+
request.add_field('Content-Encoding', 'gzip')
|
138
|
+
body = Zlib.gzip(bytes)
|
139
|
+
else
|
140
|
+
body = bytes
|
141
|
+
end
|
142
|
+
|
143
|
+
request.body = body
|
144
|
+
request.add_field('Content-Type', 'application/x-protobuf')
|
145
|
+
@headers.each { |key, value| request.add_field(key, value) }
|
146
|
+
|
147
|
+
retry_count = 0
|
148
|
+
timeout ||= @timeout
|
149
|
+
start_time = ::OpenTelemetry::Common::Utilities.timeout_timestamp
|
150
|
+
|
151
|
+
around_request do
|
152
|
+
remaining_timeout = ::OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time)
|
153
|
+
return FAILURE if remaining_timeout.zero?
|
154
|
+
|
155
|
+
@http.open_timeout = remaining_timeout
|
156
|
+
@http.read_timeout = remaining_timeout
|
157
|
+
@http.write_timeout = remaining_timeout if WRITE_TIMEOUT_SUPPORTED
|
158
|
+
@http.start unless @http.started?
|
159
|
+
response = measure_request_duration { @http.request(request) }
|
160
|
+
|
161
|
+
case response
|
162
|
+
when Net::HTTPOK
|
163
|
+
response.body # Read and discard body
|
164
|
+
SUCCESS
|
165
|
+
when Net::HTTPServiceUnavailable, Net::HTTPTooManyRequests
|
166
|
+
response.body # Read and discard body
|
167
|
+
redo if backoff?(retry_after: response['Retry-After'], retry_count: retry_count += 1, reason: response.code)
|
168
|
+
FAILURE
|
169
|
+
when Net::HTTPRequestTimeOut, Net::HTTPGatewayTimeOut, Net::HTTPBadGateway
|
170
|
+
response.body # Read and discard body
|
171
|
+
redo if backoff?(retry_count: retry_count += 1, reason: response.code)
|
172
|
+
FAILURE
|
173
|
+
when Net::HTTPNotFound
|
174
|
+
OpenTelemetry.handle_error(message: "OTLP exporter received http.code=404 for uri: '#{@path}'")
|
175
|
+
FAILURE
|
176
|
+
when Net::HTTPBadRequest, Net::HTTPClientError, Net::HTTPServerError
|
177
|
+
log_status(response.body)
|
178
|
+
FAILURE
|
179
|
+
when Net::HTTPRedirection
|
180
|
+
@http.finish
|
181
|
+
handle_redirect(response['location'])
|
182
|
+
redo if backoff?(retry_after: 0, retry_count: retry_count += 1, reason: response.code)
|
183
|
+
else
|
184
|
+
@http.finish
|
185
|
+
FAILURE
|
186
|
+
end
|
187
|
+
rescue Net::OpenTimeout, Net::ReadTimeout
|
188
|
+
retry if backoff?(retry_count: retry_count += 1, reason: 'timeout')
|
189
|
+
return FAILURE
|
190
|
+
rescue OpenSSL::SSL::SSLError
|
191
|
+
retry if backoff?(retry_count: retry_count += 1, reason: 'openssl_error')
|
192
|
+
return FAILURE
|
193
|
+
rescue SocketError
|
194
|
+
retry if backoff?(retry_count: retry_count += 1, reason: 'socket_error')
|
195
|
+
return FAILURE
|
196
|
+
rescue SystemCallError => e
|
197
|
+
retry if backoff?(retry_count: retry_count += 1, reason: e.class.name)
|
198
|
+
return FAILURE
|
199
|
+
rescue EOFError
|
200
|
+
retry if backoff?(retry_count: retry_count += 1, reason: 'eof_error')
|
201
|
+
return FAILURE
|
202
|
+
rescue Zlib::DataError
|
203
|
+
retry if backoff?(retry_count: retry_count += 1, reason: 'zlib_error')
|
204
|
+
return FAILURE
|
205
|
+
rescue StandardError => e
|
206
|
+
OpenTelemetry.handle_error(exception: e, message: 'unexpected error in OTLP::Exporter#send_bytes')
|
207
|
+
return FAILURE
|
208
|
+
end
|
209
|
+
ensure
|
210
|
+
# Reset timeouts to defaults for the next call.
|
211
|
+
@http.open_timeout = @timeout
|
212
|
+
@http.read_timeout = @timeout
|
213
|
+
@http.write_timeout = @timeout if WRITE_TIMEOUT_SUPPORTED
|
214
|
+
end
|
215
|
+
|
216
|
+
def handle_redirect(location)
|
217
|
+
# TODO: figure out destination and reinitialize @http and @path
|
218
|
+
end
|
219
|
+
|
220
|
+
def log_status(body)
|
221
|
+
status = Google::Rpc::Status.decode(body)
|
222
|
+
details = status.details.map do |detail|
|
223
|
+
klass_or_nil = ::Google::Protobuf::DescriptorPool.generated_pool.lookup(detail.type_name).msgclass
|
224
|
+
detail.unpack(klass_or_nil) if klass_or_nil
|
225
|
+
end.compact
|
226
|
+
OpenTelemetry.handle_error(message: "OTLP exporter received rpc.Status{message=#{status.message}, details=#{details}}")
|
227
|
+
rescue StandardError => e
|
228
|
+
OpenTelemetry.handle_error(exception: e, message: 'unexpected error decoding rpc.Status in OTLP::Exporter#log_status')
|
229
|
+
end
|
230
|
+
|
231
|
+
def measure_request_duration
|
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
|
242
|
+
return false if retry_count > RETRY_COUNT
|
243
|
+
|
244
|
+
sleep_interval = nil
|
245
|
+
unless retry_after.nil?
|
246
|
+
sleep_interval =
|
247
|
+
begin
|
248
|
+
Integer(retry_after)
|
249
|
+
rescue ArgumentError
|
250
|
+
nil
|
251
|
+
end
|
252
|
+
sleep_interval ||=
|
253
|
+
begin
|
254
|
+
Time.httpdate(retry_after) - Time.now
|
255
|
+
rescue # rubocop:disable Style/RescueStandardError
|
256
|
+
nil
|
257
|
+
end
|
258
|
+
sleep_interval = nil unless sleep_interval&.positive?
|
259
|
+
end
|
260
|
+
sleep_interval ||= rand(2**retry_count)
|
261
|
+
|
262
|
+
sleep(sleep_interval)
|
263
|
+
true
|
264
|
+
end
|
265
|
+
|
266
|
+
def encode(log_record_data) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
267
|
+
Opentelemetry::Proto::Collector::Logs::V1::ExportLogsServiceRequest.encode(
|
268
|
+
Opentelemetry::Proto::Collector::Logs::V1::ExportLogsServiceRequest.new(
|
269
|
+
resource_logs: log_record_data
|
270
|
+
.group_by(&:resource)
|
271
|
+
.map do |resource, log_record_datas|
|
272
|
+
Opentelemetry::Proto::Logs::V1::ResourceLogs.new(
|
273
|
+
resource: Opentelemetry::Proto::Resource::V1::Resource.new(
|
274
|
+
attributes: resource.attribute_enumerator.map { |key, value| as_otlp_key_value(key, value) }
|
275
|
+
),
|
276
|
+
scope_logs: log_record_datas
|
277
|
+
.group_by(&:instrumentation_scope)
|
278
|
+
.map do |il, lrd|
|
279
|
+
Opentelemetry::Proto::Logs::V1::ScopeLogs.new(
|
280
|
+
scope: Opentelemetry::Proto::Common::V1::InstrumentationScope.new(
|
281
|
+
name: il.name,
|
282
|
+
version: il.version
|
283
|
+
),
|
284
|
+
log_records: lrd.map { |lr| as_otlp_log_record(lr) }
|
285
|
+
)
|
286
|
+
end
|
287
|
+
)
|
288
|
+
end
|
289
|
+
)
|
290
|
+
)
|
291
|
+
rescue StandardError => e
|
292
|
+
OpenTelemetry.handle_error(exception: e, message: 'unexpected error in OTLP::Exporter#encode')
|
293
|
+
nil
|
294
|
+
end
|
295
|
+
|
296
|
+
def as_otlp_log_record(log_record_data)
|
297
|
+
Opentelemetry::Proto::Logs::V1::LogRecord.new(
|
298
|
+
time_unix_nano: log_record_data.timestamp,
|
299
|
+
observed_time_unix_nano: log_record_data.observed_timestamp,
|
300
|
+
severity_number: as_otlp_severity_number(log_record_data.severity_number),
|
301
|
+
severity_text: log_record_data.severity_text,
|
302
|
+
body: as_otlp_any_value(log_record_data.body),
|
303
|
+
attributes: log_record_data.attributes&.map { |k, v| as_otlp_key_value(k, v) },
|
304
|
+
dropped_attributes_count: log_record_data.total_recorded_attributes - log_record_data.attributes&.size.to_i,
|
305
|
+
flags: log_record_data.trace_flags.instance_variable_get(:@flags),
|
306
|
+
trace_id: log_record_data.trace_id,
|
307
|
+
span_id: log_record_data.span_id
|
308
|
+
)
|
309
|
+
end
|
310
|
+
|
311
|
+
def as_otlp_key_value(key, value)
|
312
|
+
Opentelemetry::Proto::Common::V1::KeyValue.new(key: key, value: as_otlp_any_value(value))
|
313
|
+
rescue Encoding::UndefinedConversionError => e
|
314
|
+
encoded_value = value.encode('UTF-8', invalid: :replace, undef: :replace, replace: '�')
|
315
|
+
OpenTelemetry.handle_error(exception: e, message: "encoding error for key #{key} and value #{encoded_value}")
|
316
|
+
Opentelemetry::Proto::Common::V1::KeyValue.new(key: key, value: as_otlp_any_value('Encoding Error'))
|
317
|
+
end
|
318
|
+
|
319
|
+
def as_otlp_any_value(value)
|
320
|
+
result = Opentelemetry::Proto::Common::V1::AnyValue.new
|
321
|
+
case value
|
322
|
+
when String
|
323
|
+
result.string_value = value
|
324
|
+
when Integer
|
325
|
+
result.int_value = value
|
326
|
+
when Float
|
327
|
+
result.double_value = value
|
328
|
+
when true, false
|
329
|
+
result.bool_value = value
|
330
|
+
when Array
|
331
|
+
values = value.map { |element| as_otlp_any_value(element) }
|
332
|
+
result.array_value = Opentelemetry::Proto::Common::V1::ArrayValue.new(values: values)
|
333
|
+
end
|
334
|
+
result
|
335
|
+
end
|
336
|
+
|
337
|
+
# TODO: maybe don't translate the severity number, but translate the severity text into
|
338
|
+
# the number if the number is nil? Poss. change to allow for adding your own
|
339
|
+
# otel values?
|
340
|
+
def as_otlp_severity_number(severity_number)
|
341
|
+
case severity_number
|
342
|
+
when 0 then Opentelemetry::Proto::Logs::V1::SeverityNumber::SEVERITY_NUMBER_DEBUG
|
343
|
+
when 1 then Opentelemetry::Proto::Logs::V1::SeverityNumber::SEVERITY_NUMBER_INFO
|
344
|
+
when 2 then Opentelemetry::Proto::Logs::V1::SeverityNumber::SEVERITY_NUMBER_WARN
|
345
|
+
when 3 then Opentelemetry::Proto::Logs::V1::SeverityNumber::SEVERITY_NUMBER_ERROR
|
346
|
+
when 4 then Opentelemetry::Proto::Logs::V1::SeverityNumber::SEVERITY_NUMBER_FATAL
|
347
|
+
when 5 then Opentelemetry::Proto::Logs::V1::SeverityNumber::SEVERITY_NUMBER_UNSPECIFIED
|
348
|
+
end
|
349
|
+
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
|
+
|
364
|
+
def parse_headers(raw)
|
365
|
+
entries = raw.split(',')
|
366
|
+
raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS if entries.empty?
|
367
|
+
|
368
|
+
entries.each_with_object({}) do |entry, headers|
|
369
|
+
k, v = entry.split('=', 2).map(&CGI.method(:unescape))
|
370
|
+
begin
|
371
|
+
k = k.to_s.strip
|
372
|
+
v = v.to_s.strip
|
373
|
+
rescue Encoding::CompatibilityError
|
374
|
+
raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS
|
375
|
+
rescue ArgumentError => e
|
376
|
+
raise e, ERROR_MESSAGE_INVALID_HEADERS
|
377
|
+
end
|
378
|
+
raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS if k.empty? || v.empty?
|
379
|
+
|
380
|
+
headers[k] = v
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
@@ -0,0 +1,20 @@
|
|
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 Exporter
|
12
|
+
module OTLP
|
13
|
+
## Current OpenTelemetry OTLP logs exporter version
|
14
|
+
VERSION = '0.26.3'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/collector/logs/v1/logs_service_pb.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
4
|
+
# source: opentelemetry/proto/collector/logs/v1/logs_service.proto
|
5
|
+
|
6
|
+
require 'google/protobuf'
|
7
|
+
|
8
|
+
require_relative '../../../logs/v1/logs_pb'
|
9
|
+
|
10
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
11
|
+
add_file("scout/opentelemetry/proto/collector/logs/v1/logs_service.proto", :syntax => :proto3) do
|
12
|
+
add_message "scout_apm.logging.loggers.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest" do
|
13
|
+
repeated :resource_logs, :message, 1, "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.ResourceLogs"
|
14
|
+
end
|
15
|
+
add_message "scout_apm.logging.loggers.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse" do
|
16
|
+
optional :partial_success, :message, 1, "scout_apm.logging.loggers.opentelemetry.proto.collector.logs.v1.ExportLogsPartialSuccess"
|
17
|
+
end
|
18
|
+
add_message "scout_apm.logging.loggers.opentelemetry.proto.collector.logs.v1.ExportLogsPartialSuccess" do
|
19
|
+
optional :rejected_log_records, :int64, 1
|
20
|
+
optional :error_message, :string, 2
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module ScoutApm
|
26
|
+
module Logging
|
27
|
+
module Loggers
|
28
|
+
module Opentelemetry
|
29
|
+
module Proto
|
30
|
+
module Collector
|
31
|
+
module Logs
|
32
|
+
module V1
|
33
|
+
ExportLogsServiceRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest").msgclass
|
34
|
+
ExportLogsServiceResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse").msgclass
|
35
|
+
ExportLogsPartialSuccess = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.collector.logs.v1.ExportLogsPartialSuccess").msgclass
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
4
|
+
# source: opentelemetry/proto/common/v1/common.proto
|
5
|
+
|
6
|
+
require 'google/protobuf'
|
7
|
+
|
8
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
9
|
+
add_file("scout/opentelemetry/proto/common/v1/common.proto", :syntax => :proto3) do
|
10
|
+
add_message "scout_apm.logging.loggers.opentelemetry.proto.common.v1.AnyValue" do
|
11
|
+
oneof :value do
|
12
|
+
optional :string_value, :string, 1
|
13
|
+
optional :bool_value, :bool, 2
|
14
|
+
optional :int_value, :int64, 3
|
15
|
+
optional :double_value, :double, 4
|
16
|
+
optional :array_value, :message, 5, "scout_apm.logging.loggers.opentelemetry.proto.common.v1.ArrayValue"
|
17
|
+
optional :kvlist_value, :message, 6, "scout_apm.logging.loggers.opentelemetry.proto.common.v1.KeyValueList"
|
18
|
+
optional :bytes_value, :bytes, 7
|
19
|
+
end
|
20
|
+
end
|
21
|
+
add_message "scout_apm.logging.loggers.opentelemetry.proto.common.v1.ArrayValue" do
|
22
|
+
repeated :values, :message, 1, "scout_apm.logging.loggers.opentelemetry.proto.common.v1.AnyValue"
|
23
|
+
end
|
24
|
+
add_message "scout_apm.logging.loggers.opentelemetry.proto.common.v1.KeyValueList" do
|
25
|
+
repeated :values, :message, 1, "scout_apm.logging.loggers.opentelemetry.proto.common.v1.KeyValue"
|
26
|
+
end
|
27
|
+
add_message "scout_apm.logging.loggers.opentelemetry.proto.common.v1.KeyValue" do
|
28
|
+
optional :key, :string, 1
|
29
|
+
optional :value, :message, 2, "scout_apm.logging.loggers.opentelemetry.proto.common.v1.AnyValue"
|
30
|
+
end
|
31
|
+
add_message "scout_apm.logging.loggers.opentelemetry.proto.common.v1.InstrumentationScope" do
|
32
|
+
optional :name, :string, 1
|
33
|
+
optional :version, :string, 2
|
34
|
+
repeated :attributes, :message, 3, "scout_apm.logging.loggers.opentelemetry.proto.common.v1.KeyValue"
|
35
|
+
optional :dropped_attributes_count, :uint32, 4
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module ScoutApm
|
41
|
+
module Logging
|
42
|
+
module Loggers
|
43
|
+
module Opentelemetry
|
44
|
+
module Proto
|
45
|
+
module Common
|
46
|
+
module V1
|
47
|
+
AnyValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.common.v1.AnyValue").msgclass
|
48
|
+
ArrayValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.common.v1.ArrayValue").msgclass
|
49
|
+
KeyValueList = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.common.v1.KeyValueList").msgclass
|
50
|
+
KeyValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.common.v1.KeyValue").msgclass
|
51
|
+
InstrumentationScope = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.common.v1.InstrumentationScope").msgclass
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
4
|
+
# source: opentelemetry/proto/logs/v1/logs.proto
|
5
|
+
|
6
|
+
require 'google/protobuf'
|
7
|
+
|
8
|
+
require_relative '../../common/v1/common_pb'
|
9
|
+
require_relative '../../resource/v1/resource_pb'
|
10
|
+
|
11
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
12
|
+
add_file("scout/opentelemetry/proto/logs/v1/logs.proto", :syntax => :proto3) do
|
13
|
+
add_message "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.LogsData" do
|
14
|
+
repeated :resource_logs, :message, 1, "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.ResourceLogs"
|
15
|
+
end
|
16
|
+
add_message "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.ResourceLogs" do
|
17
|
+
optional :resource, :message, 1, "scout_apm.logging.loggers.opentelemetry.proto.resource.v1.Resource"
|
18
|
+
repeated :scope_logs, :message, 2, "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.ScopeLogs"
|
19
|
+
optional :schema_url, :string, 3
|
20
|
+
end
|
21
|
+
add_message "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.ScopeLogs" do
|
22
|
+
optional :scope, :message, 1, "scout_apm.logging.loggers.opentelemetry.proto.common.v1.InstrumentationScope"
|
23
|
+
repeated :log_records, :message, 2, "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.LogRecord"
|
24
|
+
optional :schema_url, :string, 3
|
25
|
+
end
|
26
|
+
add_message "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.LogRecord" do
|
27
|
+
optional :time_unix_nano, :fixed64, 1
|
28
|
+
optional :observed_time_unix_nano, :fixed64, 11
|
29
|
+
optional :severity_number, :enum, 2, "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.SeverityNumber"
|
30
|
+
optional :severity_text, :string, 3
|
31
|
+
optional :body, :message, 5, "scout_apm.logging.loggers.opentelemetry.proto.common.v1.AnyValue"
|
32
|
+
repeated :attributes, :message, 6, "scout_apm.logging.loggers.opentelemetry.proto.common.v1.KeyValue"
|
33
|
+
optional :dropped_attributes_count, :uint32, 7
|
34
|
+
optional :flags, :fixed32, 8
|
35
|
+
optional :trace_id, :bytes, 9
|
36
|
+
optional :span_id, :bytes, 10
|
37
|
+
end
|
38
|
+
add_enum "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.SeverityNumber" do
|
39
|
+
value :SEVERITY_NUMBER_UNSPECIFIED, 0
|
40
|
+
value :SEVERITY_NUMBER_TRACE, 1
|
41
|
+
value :SEVERITY_NUMBER_TRACE2, 2
|
42
|
+
value :SEVERITY_NUMBER_TRACE3, 3
|
43
|
+
value :SEVERITY_NUMBER_TRACE4, 4
|
44
|
+
value :SEVERITY_NUMBER_DEBUG, 5
|
45
|
+
value :SEVERITY_NUMBER_DEBUG2, 6
|
46
|
+
value :SEVERITY_NUMBER_DEBUG3, 7
|
47
|
+
value :SEVERITY_NUMBER_DEBUG4, 8
|
48
|
+
value :SEVERITY_NUMBER_INFO, 9
|
49
|
+
value :SEVERITY_NUMBER_INFO2, 10
|
50
|
+
value :SEVERITY_NUMBER_INFO3, 11
|
51
|
+
value :SEVERITY_NUMBER_INFO4, 12
|
52
|
+
value :SEVERITY_NUMBER_WARN, 13
|
53
|
+
value :SEVERITY_NUMBER_WARN2, 14
|
54
|
+
value :SEVERITY_NUMBER_WARN3, 15
|
55
|
+
value :SEVERITY_NUMBER_WARN4, 16
|
56
|
+
value :SEVERITY_NUMBER_ERROR, 17
|
57
|
+
value :SEVERITY_NUMBER_ERROR2, 18
|
58
|
+
value :SEVERITY_NUMBER_ERROR3, 19
|
59
|
+
value :SEVERITY_NUMBER_ERROR4, 20
|
60
|
+
value :SEVERITY_NUMBER_FATAL, 21
|
61
|
+
value :SEVERITY_NUMBER_FATAL2, 22
|
62
|
+
value :SEVERITY_NUMBER_FATAL3, 23
|
63
|
+
value :SEVERITY_NUMBER_FATAL4, 24
|
64
|
+
end
|
65
|
+
add_enum "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.LogRecordFlags" do
|
66
|
+
value :LOG_RECORD_FLAGS_DO_NOT_USE, 0
|
67
|
+
value :LOG_RECORD_FLAGS_TRACE_FLAGS_MASK, 255
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
module ScoutApm
|
73
|
+
module Logging
|
74
|
+
module Loggers
|
75
|
+
module Opentelemetry
|
76
|
+
module Proto
|
77
|
+
module Logs
|
78
|
+
module V1
|
79
|
+
LogsData = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.logs.v1.LogsData").msgclass
|
80
|
+
ResourceLogs = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.logs.v1.ResourceLogs").msgclass
|
81
|
+
ScopeLogs = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.logs.v1.ScopeLogs").msgclass
|
82
|
+
LogRecord = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.logs.v1.LogRecord").msgclass
|
83
|
+
SeverityNumber = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.logs.v1.SeverityNumber").enummodule
|
84
|
+
LogRecordFlags = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.logs.v1.LogRecordFlags").enummodule
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|