epsagon 0.0.23 → 0.0.28
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/lib/arn_parser.rb +27 -0
- data/lib/epsagon.rb +164 -31
- data/lib/epsagon_constants.rb +1 -1
- data/lib/exporter_extension.rb +78 -0
- data/lib/instrumentation/aws_sdk_plugin.rb +116 -5
- data/lib/instrumentation/epsagon_faraday_middleware.rb +5 -5
- data/lib/instrumentation/epsagon_rails_middleware.rb +2 -2
- data/lib/instrumentation/net_http.rb +2 -0
- data/lib/instrumentation/postgres.rb +294 -0
- data/lib/instrumentation/version.rb +0 -0
- data/lib/util.rb +9 -0
- metadata +25 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a17b55f2ce2ff35ca70dd51e8f8337041e202c528b6c691800d3e3b7b0d42c4
|
4
|
+
data.tar.gz: d93046c571faa2d35a681217cb12952c57c7bd3232e77a136a5954a5534541b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9290a8dee89f24588f93bca0ab331855cab0cd46e09fdae10c4bb0c00c7c61c35c6045bc6b41971639767fc46f30d241b49a45a89a51c29baaa734113188aa7a
|
7
|
+
data.tar.gz: 3c4c1b54d0b344bf7399f3d088cd22bebca010d2b063244b425693bcaa2ef0ced625ec3a0921d1d5ce0cf7effd81e90dc6e410d5c893f1062096c78ec66e795f
|
data/lib/arn_parser.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#
|
2
|
+
# Credit: https://gist.github.com/RulerOf/b9f5dd00a9911aba8271b57d3d269d7a
|
3
|
+
#
|
4
|
+
class Arn
|
5
|
+
attr_accessor :partition, :service, :region, :account, :resource
|
6
|
+
|
7
|
+
def initialize(partition, service, region, account, resource)
|
8
|
+
@partition = partition
|
9
|
+
@service = service
|
10
|
+
@region = region
|
11
|
+
@account = account
|
12
|
+
@resource = resource
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.parse(arn)
|
16
|
+
raise TypeError, 'ARN must be supplied as a string' unless arn.is_a?(String)
|
17
|
+
|
18
|
+
arn_components = arn.split(':', 6)
|
19
|
+
raise ArgumentError, 'Could not parse ARN' if arn_components.length < 6
|
20
|
+
|
21
|
+
Arn.new arn_components[1],
|
22
|
+
arn_components[2],
|
23
|
+
arn_components[3],
|
24
|
+
arn_components[4],
|
25
|
+
arn_components[5]
|
26
|
+
end
|
27
|
+
end
|
data/lib/epsagon.rb
CHANGED
@@ -1,70 +1,98 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
require 'json'
|
3
3
|
require 'rubygems'
|
4
4
|
require 'net/http'
|
5
5
|
require 'bundler/setup'
|
6
6
|
require 'opentelemetry/sdk'
|
7
7
|
require 'opentelemetry/exporter/otlp'
|
8
|
+
require 'opentelemetry/instrumentation/sidekiq'
|
8
9
|
|
9
10
|
require_relative 'instrumentation/sinatra'
|
10
11
|
require_relative 'instrumentation/net_http'
|
11
12
|
require_relative 'instrumentation/faraday'
|
12
13
|
require_relative 'instrumentation/aws_sdk'
|
13
14
|
require_relative 'instrumentation/rails'
|
15
|
+
require_relative 'instrumentation/postgres'
|
14
16
|
require_relative 'util'
|
15
17
|
require_relative 'epsagon_constants'
|
18
|
+
require_relative 'exporter_extension'
|
19
|
+
require_relative 'arn_parser'
|
16
20
|
|
17
21
|
Bundler.require
|
18
22
|
|
19
23
|
# Epsagon tracing main entry point
|
20
24
|
module Epsagon
|
21
|
-
|
22
25
|
DEFAULT_BACKEND = 'opentelemetry.tc.epsagon.com:443/traces'
|
26
|
+
DEFAULT_IGNORE_DOMAINS = ['newrelic.com'].freeze
|
23
27
|
|
24
|
-
@@epsagon_config =
|
25
|
-
metadata_only: ENV['EPSAGON_METADATA']&.to_s&.downcase != 'false',
|
26
|
-
debug: ENV['EPSAGON_DEBUG']&.to_s&.downcase == 'true',
|
27
|
-
token: ENV['EPSAGON_TOKEN'],
|
28
|
-
app_name: ENV['EPSAGON_APP_NAME'],
|
29
|
-
max_attribute_size: ENV['EPSAGON_MAX_ATTRIBUTE_SIZE'] || 5000,
|
30
|
-
backend: ENV['EPSAGON_BACKEND'] || DEFAULT_BACKEND
|
31
|
-
}
|
28
|
+
@@epsagon_config = nil
|
32
29
|
|
33
30
|
module_function
|
34
31
|
|
35
32
|
def init(**args)
|
36
|
-
|
33
|
+
get_config.merge!(args)
|
37
34
|
OpenTelemetry::SDK.configure
|
38
35
|
end
|
39
36
|
|
40
37
|
def get_config
|
41
|
-
@@epsagon_config
|
38
|
+
@@epsagon_config ||= {
|
39
|
+
metadata_only: ENV['EPSAGON_METADATA']&.to_s&.downcase != 'false',
|
40
|
+
debug: ENV['EPSAGON_DEBUG']&.to_s&.downcase == 'true',
|
41
|
+
token: ENV['EPSAGON_TOKEN'] || '',
|
42
|
+
app_name: ENV['EPSAGON_APP_NAME'] || '',
|
43
|
+
max_attribute_size: ENV['EPSAGON_MAX_ATTRIBUTE_SIZE'] || 5000,
|
44
|
+
backend: ENV['EPSAGON_BACKEND'] || DEFAULT_BACKEND,
|
45
|
+
ignore_domains: ENV['EPSAGON_IGNORE_DOMAINS'] || DEFAULT_IGNORE_DOMAINS
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_ecs_metadata
|
50
|
+
metadata_uri = ENV['ECS_CONTAINER_METADATA_URI']
|
51
|
+
return {} if metadata_uri.nil?
|
52
|
+
|
53
|
+
response = Net::HTTP.get(URI(metadata_uri))
|
54
|
+
ecs_metadata = JSON.parse(response)
|
55
|
+
arn = Arn.parse(ecs_metadata['Labels']['com.amazonaws.ecs.task-arn'])
|
56
|
+
|
57
|
+
{
|
58
|
+
'aws.account_id' => arn.account,
|
59
|
+
'aws.region' => arn.region,
|
60
|
+
'aws.ecs.cluster' => ecs_metadata['Labels']['com.amazonaws.ecs.cluster'],
|
61
|
+
'aws.ecs.task_arn' => ecs_metadata['Labels']['com.amazonaws.ecs.task-arn'],
|
62
|
+
'aws.ecs.container_name' => ecs_metadata['Labels']['com.amazonaws.ecs.container-name'],
|
63
|
+
'aws.ecs.task.family' => ecs_metadata['Labels']['com.amazonaws.ecs.task-definition-family'],
|
64
|
+
'aws.ecs.task.revision' => ecs_metadata['Labels']['com.amazonaws.ecs.task-definition-version']
|
65
|
+
}
|
42
66
|
end
|
43
67
|
|
44
68
|
# config opentelemetry with epsaon extensions:
|
45
69
|
|
46
70
|
def epsagon_confs(configurator)
|
71
|
+
otel_resource = {
|
72
|
+
'application' => get_config[:app_name],
|
73
|
+
'epsagon.version' => EpsagonConstants::VERSION,
|
74
|
+
'epsagon.metadata_only' => get_config[:metadata_only]
|
75
|
+
}.merge(set_ecs_metadata)
|
76
|
+
|
47
77
|
configurator.resource = OpenTelemetry::SDK::Resources::Resource.telemetry_sdk.merge(
|
48
|
-
OpenTelemetry::SDK::Resources::Resource.create(
|
49
|
-
'application' => @@epsagon_config[:app_name],
|
50
|
-
'epsagon.version' => EpsagonConstants::VERSION
|
51
|
-
})
|
78
|
+
OpenTelemetry::SDK::Resources::Resource.create(otel_resource)
|
52
79
|
)
|
53
|
-
configurator.use 'EpsagonSinatraInstrumentation', { epsagon: @@epsagon_config }
|
54
|
-
configurator.use 'EpsagonNetHTTPInstrumentation', { epsagon: @@epsagon_config }
|
55
|
-
configurator.use 'EpsagonFaradayInstrumentation', { epsagon: @@epsagon_config }
|
56
|
-
configurator.use 'EpsagonAwsSdkInstrumentation', { epsagon: @@epsagon_config }
|
57
|
-
configurator.use 'EpsagonRailsInstrumentation', { epsagon: @@epsagon_config }
|
58
|
-
# configurator.use 'OpenTelemetry::Instrumentation::Sidekiq'
|
59
80
|
|
81
|
+
configurator.use 'EpsagonSinatraInstrumentation', { epsagon: get_config }
|
82
|
+
configurator.use 'EpsagonNetHTTPInstrumentation', { epsagon: get_config }
|
83
|
+
configurator.use 'EpsagonFaradayInstrumentation', { epsagon: get_config }
|
84
|
+
configurator.use 'EpsagonAwsSdkInstrumentation', { epsagon: get_config }
|
85
|
+
configurator.use 'EpsagonRailsInstrumentation', { epsagon: get_config }
|
86
|
+
configurator.use 'OpenTelemetry::Instrumentation::Sidekiq', { epsagon: get_config }
|
87
|
+
configurator.use 'EpsagonPostgresInstrumentation', { epsagon: get_config }
|
60
88
|
|
61
|
-
if
|
89
|
+
if get_config[:debug]
|
62
90
|
configurator.add_span_processor OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
|
63
91
|
OpenTelemetry::Exporter::OTLP::Exporter.new(headers: {
|
64
|
-
'x-epsagon-token' =>
|
92
|
+
'x-epsagon-token' => get_config[:token]
|
65
93
|
},
|
66
|
-
endpoint:
|
67
|
-
insecure:
|
94
|
+
endpoint: get_config[:backend],
|
95
|
+
insecure: get_config[:insecure] || false)
|
68
96
|
)
|
69
97
|
|
70
98
|
configurator.add_span_processor OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
|
@@ -73,10 +101,10 @@ module Epsagon
|
|
73
101
|
else
|
74
102
|
configurator.add_span_processor OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
|
75
103
|
exporter: OpenTelemetry::Exporter::OTLP::Exporter.new(headers: {
|
76
|
-
'x-epsagon-token' =>
|
104
|
+
'x-epsagon-token' => get_config[:token]
|
77
105
|
},
|
78
|
-
endpoint:
|
79
|
-
insecure:
|
106
|
+
endpoint: get_config[:backend],
|
107
|
+
insecure: get_config[:insecure] || false)
|
80
108
|
)
|
81
109
|
end
|
82
110
|
end
|
@@ -97,15 +125,89 @@ module SpanExtension
|
|
97
125
|
def initialize(*args)
|
98
126
|
super(*args)
|
99
127
|
if @attributes
|
100
|
-
@attributes = Hash[@attributes.map { |k,v|
|
128
|
+
@attributes = Hash[@attributes.select {|k,v| not BLANKS.include? v}.map { |k,v|
|
101
129
|
[k, Util.trim_attr(v, Epsagon.get_config[:max_attribute_size])]
|
102
130
|
}]
|
103
131
|
end
|
104
|
-
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
module SidekiqClientMiddlewareExtension
|
136
|
+
def call(_worker_class, job, _queue, _redis_pool)
|
137
|
+
config = OpenTelemetry::Instrumentation::Sidekiq::Instrumentation.instance.config[:epsagon] || {}
|
138
|
+
attributes = {
|
139
|
+
'operation' => job['at'] ? 'perform_at' : 'perform_async',
|
140
|
+
'messaging.system' => 'sidekiq',
|
141
|
+
'messaging.sidekiq.job_class' => job['wrapped']&.to_s || job['class'],
|
142
|
+
'messaging.message_id' => job['jid'],
|
143
|
+
'messaging.destination' => job['queue'],
|
144
|
+
'messaging.destination_kind' => 'queue',
|
145
|
+
'messaging.sidekiq.redis_url' => Sidekiq.options['url'] || Util.redis_default_url
|
146
|
+
}
|
147
|
+
unless config[:metadata_only]
|
148
|
+
attributes.merge!({
|
149
|
+
'messaging.sidekiq.args' => JSON.dump(job['args'])
|
150
|
+
})
|
151
|
+
end
|
152
|
+
tracer.in_span(
|
153
|
+
job['queue'],
|
154
|
+
attributes: attributes,
|
155
|
+
kind: :producer
|
156
|
+
) do |span|
|
157
|
+
OpenTelemetry.propagation.text.inject(job)
|
158
|
+
span.add_event('created_at', timestamp: job['created_at'])
|
159
|
+
Util.untraced {yield}
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
module SidekiqServerMiddlewareExtension
|
165
|
+
def call(_worker, msg, _queue)
|
166
|
+
inner_exception = nil
|
167
|
+
config = OpenTelemetry::Instrumentation::Sidekiq::Instrumentation.instance.config[:epsagon] || {}
|
168
|
+
parent_context = OpenTelemetry.propagation.text.extract(msg)
|
169
|
+
attributes = {
|
170
|
+
'operation' => 'perform',
|
171
|
+
'messaging.system' => 'sidekiq',
|
172
|
+
'messaging.sidekiq.job_class' => msg['wrapped']&.to_s || msg['class'],
|
173
|
+
'messaging.message_id' => msg['jid'],
|
174
|
+
'messaging.destination' => msg['queue'],
|
175
|
+
'messaging.destination_kind' => 'queue',
|
176
|
+
'messaging.sidekiq.redis_url' => Sidekiq.options['url'] || Util.redis_default_url
|
177
|
+
}
|
178
|
+
runner_attributes = {
|
179
|
+
'type' => 'sidekiq_worker',
|
180
|
+
'messaging.sidekiq.redis_url' => Sidekiq.options['url'] || Util.redis_default_url,
|
181
|
+
|
182
|
+
}
|
183
|
+
unless config[:metadata_only]
|
184
|
+
attributes.merge!({
|
185
|
+
'messaging.sidekiq.args' => JSON.dump(msg['args'])
|
186
|
+
})
|
187
|
+
end
|
188
|
+
tracer.in_span(
|
189
|
+
msg['queue'],
|
190
|
+
attributes: attributes,
|
191
|
+
with_parent: parent_context,
|
192
|
+
kind: :consumer
|
193
|
+
) do |trigger_span|
|
194
|
+
trigger_span.add_event('created_at', timestamp: msg['created_at'])
|
195
|
+
trigger_span.add_event('enqueued_at', timestamp: msg['enqueued_at'])
|
196
|
+
tracer.in_span(msg['wrapped']&.to_s || msg['class'],
|
197
|
+
attributes: runner_attributes,
|
198
|
+
kind: :consumer
|
199
|
+
) do |runner_span|
|
200
|
+
yield
|
201
|
+
end
|
202
|
+
rescue Exception => e
|
203
|
+
inner_exception = e
|
204
|
+
end
|
205
|
+
raise inner_exception if inner_exception
|
105
206
|
end
|
106
207
|
end
|
107
208
|
|
108
209
|
# monkey patch to include epsagon confs
|
210
|
+
|
109
211
|
module OpenTelemetry
|
110
212
|
# monkey patch inner SDK module
|
111
213
|
module SDK
|
@@ -122,4 +224,35 @@ module OpenTelemetry
|
|
122
224
|
end
|
123
225
|
end
|
124
226
|
end
|
227
|
+
module Instrumentation
|
228
|
+
module Sidekiq
|
229
|
+
class Instrumentation
|
230
|
+
def add_server_middleware
|
231
|
+
::Sidekiq.configure_server do |config|
|
232
|
+
config.server_middleware do |chain|
|
233
|
+
chain.add Middlewares::Server::TracerMiddleware
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
if defined?(::Sidekiq::Testing) # rubocop:disable Style/GuardClause
|
238
|
+
::Sidekiq::Testing.server_middleware do |chain|
|
239
|
+
chain.add Middlewares::Server::TracerMiddleware
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
module Middlewares
|
245
|
+
module Client
|
246
|
+
class TracerMiddleware
|
247
|
+
prepend SidekiqClientMiddlewareExtension
|
248
|
+
end
|
249
|
+
end
|
250
|
+
module Server
|
251
|
+
class TracerMiddleware
|
252
|
+
prepend SidekiqServerMiddlewareExtension
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
125
258
|
end
|
data/lib/epsagon_constants.rb
CHANGED
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module OpenTelemetry
|
4
|
+
module Exporter
|
5
|
+
module OTLP
|
6
|
+
# An OpenTelemetry trace exporter that sends spans over HTTP as Protobuf encoded OTLP ExportTraceServiceRequests.
|
7
|
+
class Exporter # rubocop:disable Metrics/ClassLength
|
8
|
+
def send_bytes(bytes, timeout:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
9
|
+
retry_count = 0
|
10
|
+
timeout ||= @timeout
|
11
|
+
start_time = Time.now
|
12
|
+
untraced do # rubocop:disable Metrics/BlockLength
|
13
|
+
request = Net::HTTP::Post.new(@path)
|
14
|
+
request.body = if @compression == 'gzip'
|
15
|
+
request.add_field('Content-Encoding', 'gzip')
|
16
|
+
Zlib.gzip(bytes)
|
17
|
+
else
|
18
|
+
bytes
|
19
|
+
end
|
20
|
+
request.add_field('Content-Type', 'application/x-protobuf')
|
21
|
+
@headers&.each { |key, value| request.add_field(key, value) }
|
22
|
+
|
23
|
+
remaining_timeout = OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time)
|
24
|
+
return TIMEOUT if remaining_timeout.zero?
|
25
|
+
|
26
|
+
@http.open_timeout = remaining_timeout
|
27
|
+
@http.read_timeout = remaining_timeout
|
28
|
+
@http.write_timeout = remaining_timeout if WRITE_TIMEOUT_SUPPORTED
|
29
|
+
@http.start unless @http.started?
|
30
|
+
response = measure_request_duration { @http.request(request) }
|
31
|
+
|
32
|
+
case response
|
33
|
+
when Net::HTTPOK
|
34
|
+
response.body # Read and discard body
|
35
|
+
SUCCESS
|
36
|
+
when Net::HTTPServiceUnavailable, Net::HTTPTooManyRequests
|
37
|
+
response.body # Read and discard body
|
38
|
+
redo if backoff?(retry_after: response['Retry-After'], retry_count: retry_count += 1, reason: response.code)
|
39
|
+
FAILURE
|
40
|
+
when Net::HTTPRequestTimeOut, Net::HTTPGatewayTimeOut, Net::HTTPBadGateway
|
41
|
+
response.body # Read and discard body
|
42
|
+
redo if backoff?(retry_count: retry_count += 1, reason: response.code)
|
43
|
+
FAILURE
|
44
|
+
when Net::HTTPBadRequest, Net::HTTPClientError, Net::HTTPServerError
|
45
|
+
# TODO: decode the body as a google.rpc.Status Protobuf-encoded message when https://github.com/open-telemetry/opentelemetry-collector/issues/1357 is fixed.
|
46
|
+
response.body # Read and discard body
|
47
|
+
FAILURE
|
48
|
+
when Net::HTTPRedirection
|
49
|
+
@http.finish
|
50
|
+
handle_redirect(response['location'])
|
51
|
+
redo if backoff?(retry_after: 0, retry_count: retry_count += 1, reason: response.code)
|
52
|
+
else
|
53
|
+
@http.finish
|
54
|
+
FAILURE
|
55
|
+
end
|
56
|
+
rescue Net::OpenTimeout, Net::ReadTimeout
|
57
|
+
puts "Epsagon: timeout while sending trace" if Epsagon.get_config[:debug]
|
58
|
+
retry if backoff?(retry_count: retry_count += 1, reason: 'timeout')
|
59
|
+
return FAILURE
|
60
|
+
ensure
|
61
|
+
if Epsagon.get_config[:debug] && response && response.code.to_i >= 400
|
62
|
+
puts "Epsagon: Error while sending trace:"
|
63
|
+
puts "#{response.code} #{response.class.name} #{response.message}"
|
64
|
+
puts "Headers: #{response.to_hash.inspect}"
|
65
|
+
puts response.body
|
66
|
+
end
|
67
|
+
end
|
68
|
+
ensure
|
69
|
+
# Reset timeouts to defaults for the next call.
|
70
|
+
@http.open_timeout = @timeout
|
71
|
+
@http.read_timeout = @timeout
|
72
|
+
@http.write_timeout = @timeout if WRITE_TIMEOUT_SUPPORTED
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -4,6 +4,7 @@ require 'aws-sdk-core'
|
|
4
4
|
require 'opentelemetry/common'
|
5
5
|
require 'opentelemetry/sdk'
|
6
6
|
|
7
|
+
|
7
8
|
def untraced(&block)
|
8
9
|
OpenTelemetry::Trace.with_span(OpenTelemetry::Trace::Span.new, &block)
|
9
10
|
end
|
@@ -17,14 +18,120 @@ end
|
|
17
18
|
|
18
19
|
# Generates Spans for all uses of AWS SDK
|
19
20
|
class EpsagonAwsHandler < Seahorse::Client::Handler
|
21
|
+
SPAN_KIND = {
|
22
|
+
'ReceiveMessage' => :consumer,
|
23
|
+
'SendMessage' => :producer,
|
24
|
+
'SendMessageBatch' => :producer,
|
25
|
+
'Publish' => :producer,
|
26
|
+
}
|
27
|
+
|
20
28
|
def call(context)
|
21
|
-
|
29
|
+
span_name = ''
|
30
|
+
span_kind = :client
|
31
|
+
attributes = {
|
32
|
+
'aws.service' => context.client.class.to_s.split('::')[1].downcase,
|
33
|
+
'aws.operation' => context.operation.name
|
34
|
+
}
|
35
|
+
attributes['aws.region'] = context.client.config.region unless attributes['aws.service'] == 's3'
|
36
|
+
|
37
|
+
span_kind = SPAN_KIND[attributes['aws.operation']] || span_kind
|
38
|
+
if attributes['aws.service'] == 's3'
|
39
|
+
attributes['aws.s3.bucket'] = context.params[:bucket]
|
40
|
+
span_name = attributes['aws.s3.bucket'] if attributes['aws.s3.bucket']
|
41
|
+
attributes['aws.s3.key'] = context.params[:key]
|
42
|
+
attributes['aws.s3.copy_source'] = context.params[:copy_source]
|
43
|
+
elsif attributes['aws.service'] == 'sqs'
|
44
|
+
queue_url = context.params[:queue_url]
|
45
|
+
queue_name = queue_url ? queue_url[queue_url.rindex('/')+1..-1] : context.params[:queue_name]
|
46
|
+
attributes['aws.sqs.max_number_of_messages'] = context.params[:max_number_of_messages]
|
47
|
+
attributes['aws.sqs.wait_time_seconds'] = context.params[:wait_time_seconds]
|
48
|
+
attributes['aws.sqs.visibility_timeout'] = context.params[:visibility_timeout]
|
49
|
+
attributes['aws.sqs.message_id'] = context.params[:message_id]
|
50
|
+
if queue_name
|
51
|
+
attributes['aws.sqs.queue_name'] = queue_name
|
52
|
+
span_name = attributes['aws.sqs.queue_name'] if attributes['aws.sqs.queue_name']
|
53
|
+
end
|
54
|
+
unless config[:epsagon][:metadata_only]
|
55
|
+
if attributes['aws.operation'] == 'SendMessageBatch'
|
56
|
+
messages_attributes = context.params[:entries].map do |m|
|
57
|
+
record = {
|
58
|
+
'message_attributes' => m[:message_attributes].map {|k,v| [k, v.to_h]},
|
59
|
+
'message_body' => m[:message_body],
|
60
|
+
}
|
61
|
+
end
|
62
|
+
attributes['aws.sqs.record'] = JSON.dump(messages_attributes) if messages_attributes
|
63
|
+
end
|
64
|
+
attributes['aws.sqs.record.message_body'] = context.params[:message_body]
|
65
|
+
attributes['aws.sqs.record.message_attributes'] = JSON.dump(context.params[:message_attributes]) if context.params[:message_attributes]
|
66
|
+
end
|
67
|
+
elsif attributes['aws.service'] == 'sns'
|
68
|
+
topic_arn = context.params[:topic_arn]
|
69
|
+
topic_name = topic_arn ? topic_arn[topic_arn.rindex(':')+1..-1] : context.params[:name]
|
70
|
+
span_name = attributes['aws.sns.topic_name'] = topic_name if topic_name
|
71
|
+
unless config[:epsagon][:metadata_only]
|
72
|
+
attributes['aws.sns.subject'] = context.params[:subject]
|
73
|
+
attributes['aws.sns.message'] = context.params[:message]
|
74
|
+
attributes['aws.sns.message_attributes'] = JSON.dump(context.params[:message_attributes]) if context.params[:message_attributes]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
tracer.in_span(span_name, kind: span_kind, attributes: attributes) do |span|
|
22
78
|
untraced do
|
23
|
-
@handler.call(context).tap do
|
24
|
-
|
25
|
-
|
26
|
-
|
79
|
+
@handler.call(context).tap do |result|
|
80
|
+
if attributes['aws.service'] == 's3'
|
81
|
+
modified = context.http_response.headers[:'last-modified']
|
82
|
+
reformatted_modified = modified ?
|
83
|
+
Time.strptime(modified, '%a, %d %b %Y %H:%M:%S %Z')
|
84
|
+
.strftime('%Y-%m-%dT%H:%M:%SZ') :
|
85
|
+
nil
|
86
|
+
if context.operation.name == 'GetObject'
|
87
|
+
span.set_attribute('aws.s3.content_length', context.http_response.headers[:'content-length']&.to_i)
|
88
|
+
end
|
89
|
+
span.set_attribute('aws.s3.etag', context.http_response.headers[:etag]&.tr('"',''))
|
90
|
+
span.set_attribute('aws.s3.last_modified', reformatted_modified)
|
91
|
+
elsif attributes['aws.service'] == 'sqs'
|
92
|
+
if context.operation.name == 'SendMessage'
|
93
|
+
span.set_attribute('aws.sqs.record.message_id', result.message_id)
|
94
|
+
end
|
95
|
+
if context.operation.name == 'SendMessageBatch'
|
96
|
+
messages_attributes = result.successful.map do |m|
|
97
|
+
record = {'message_id' => m.message_id}
|
98
|
+
unless config[:epsagon][:metadata_only]
|
99
|
+
context.params[:entries].each do |e|
|
100
|
+
record.merge!({
|
101
|
+
'message_attributes' => e[:message_attributes].map {|k,v| [k, v.to_h]},
|
102
|
+
'message_body' => e[:message_body],
|
103
|
+
}) if e[:id] == m.id
|
104
|
+
end
|
105
|
+
end
|
106
|
+
record
|
107
|
+
end
|
108
|
+
span.set_attribute('aws.sqs.record', JSON.dump(messages_attributes)) if messages_attributes
|
109
|
+
end
|
110
|
+
if context.operation.name == 'ReceiveMessage'
|
111
|
+
messages_attributes = result.messages.map do |m|
|
112
|
+
record = {
|
113
|
+
'message_id' => m.message_id,
|
114
|
+
'attributes' => {
|
115
|
+
'sender_id' => m.attributes['SenderId'],
|
116
|
+
'sent_timestamp' => m.attributes['SentTimestamp'],
|
117
|
+
'aws_trace_header' => m.attributes['AWSTraceHeader'],
|
118
|
+
}
|
119
|
+
}
|
120
|
+
unless config[:epsagon][:metadata_only]
|
121
|
+
record['message_attributes'] = m.message_attributes.map {|k,v| [k, v.to_h]}
|
122
|
+
record['message_body'] = m.body
|
123
|
+
end
|
124
|
+
record
|
125
|
+
end
|
126
|
+
span.set_attribute('aws.sqs.record', JSON.dump(messages_attributes)) if messages_attributes
|
127
|
+
end
|
128
|
+
elsif attributes['aws.service'] == 'sns'
|
129
|
+
span.set_attribute('aws.sns.message_id', result.message_id) if context.operation.name == 'Publish'
|
130
|
+
end
|
27
131
|
span.set_attribute('http.status_code', context.http_response.status_code)
|
132
|
+
span.status = OpenTelemetry::Trace::Status.http_to_status(
|
133
|
+
context.http_response.status_code
|
134
|
+
)
|
28
135
|
end
|
29
136
|
end
|
30
137
|
end
|
@@ -33,4 +140,8 @@ class EpsagonAwsHandler < Seahorse::Client::Handler
|
|
33
140
|
def tracer
|
34
141
|
EpsagonAwsSdkInstrumentation.instance.tracer()
|
35
142
|
end
|
143
|
+
|
144
|
+
def config
|
145
|
+
EpsagonAwsSdkInstrumentation.instance.config
|
146
|
+
end
|
36
147
|
end
|
@@ -37,11 +37,11 @@ class EpsagonFaradayMiddleware < ::Faraday::Middleware
|
|
37
37
|
unless config[:epsagon][:metadata_only]
|
38
38
|
attributes.merge!(Util.epsagon_query_attributes(env.url.query))
|
39
39
|
attributes.merge!({
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
40
|
+
'http.request.path_params' => path_params,
|
41
|
+
'http.request.headers' => env.request_headers.to_json,
|
42
|
+
'http.request.body' => env.body,
|
43
|
+
'http.request.headers.User-Agent' => env.request_headers['User-Agent']
|
44
|
+
})
|
45
45
|
end
|
46
46
|
|
47
47
|
tracer.in_span(
|
@@ -130,7 +130,7 @@ class EpsagonRackMiddleware
|
|
130
130
|
def request_span_attributes(env:)
|
131
131
|
request = Rack::Request.new(env)
|
132
132
|
path, path_params = request.path.split(';')
|
133
|
-
request_headers = JSON.generate(Hash[*env.select { |k, _v| k.start_with? 'HTTP_' }
|
133
|
+
request_headers = JSON.generate(Hash[*env.select { |k, _v| k.to_s.start_with? 'HTTP_' }
|
134
134
|
.collect { |k, v| [k.sub(/^HTTP_/, ''), v] }
|
135
135
|
.collect { |k, v| [k.split('_').collect(&:capitalize).join('-'), v] }
|
136
136
|
.sort
|
@@ -189,7 +189,7 @@ class EpsagonRackMiddleware
|
|
189
189
|
def set_attributes_after_request(http_span, _framework_span, status, headers, response)
|
190
190
|
unless config[:epsagon][:metadata_only]
|
191
191
|
http_span.set_attribute('http.response.headers', JSON.generate(headers))
|
192
|
-
http_span.set_attribute('http.response.body', response.join)
|
192
|
+
http_span.set_attribute('http.response.body', response.join) if response.respond_to?(:join)
|
193
193
|
end
|
194
194
|
|
195
195
|
http_span.set_attribute('http.status_code', status)
|
@@ -17,6 +17,7 @@ module EpsagonNetHTTPExtension
|
|
17
17
|
def request(req, body = nil, &block)
|
18
18
|
# Do not trace recursive call for starting the connection
|
19
19
|
return super(req, body, &block) unless started?
|
20
|
+
return super(req, body, &block) if config[:epsagon][:ignore_domains].any? {|d| @address.include? d}
|
20
21
|
|
21
22
|
attributes = Hash[OpenTelemetry::Common::HTTP::ClientContext.attributes]
|
22
23
|
path_with_params, query = req.path.split('?')
|
@@ -55,6 +56,7 @@ module EpsagonNetHTTPExtension
|
|
55
56
|
|
56
57
|
def annotate_span_with_response!(span, response)
|
57
58
|
return unless response&.code
|
59
|
+
return unless span.respond_to?(:set_attribute)
|
58
60
|
|
59
61
|
status_code = response.code.to_i
|
60
62
|
|
@@ -0,0 +1,294 @@
|
|
1
|
+
require 'pg_query'
|
2
|
+
|
3
|
+
module PostgresExtension
|
4
|
+
# A list of SQL commands, from: https://www.postgresql.org/docs/current/sql-commands.html
|
5
|
+
# Commands are truncated to their first word, and all duplicates
|
6
|
+
# are removed, This favors brevity and low-cardinality over descriptiveness.
|
7
|
+
SQL_COMMANDS = %w[
|
8
|
+
ABORT
|
9
|
+
ALTER
|
10
|
+
ANALYZE
|
11
|
+
BEGIN
|
12
|
+
CALL
|
13
|
+
CHECKPOINT
|
14
|
+
CLOSE
|
15
|
+
CLUSTER
|
16
|
+
COMMENT
|
17
|
+
COMMIT
|
18
|
+
COPY
|
19
|
+
CREATE
|
20
|
+
DEALLOCATE
|
21
|
+
DECLARE
|
22
|
+
DELETE
|
23
|
+
DISCARD
|
24
|
+
DO
|
25
|
+
DROP
|
26
|
+
END
|
27
|
+
EXECUTE
|
28
|
+
EXPLAIN
|
29
|
+
FETCH
|
30
|
+
GRANT
|
31
|
+
IMPORT
|
32
|
+
INSERT
|
33
|
+
LISTEN
|
34
|
+
LOAD
|
35
|
+
LOCK
|
36
|
+
MOVE
|
37
|
+
NOTIFY
|
38
|
+
PREPARE
|
39
|
+
PREPARE
|
40
|
+
REASSIGN
|
41
|
+
REFRESH
|
42
|
+
REINDEX
|
43
|
+
RELEASE
|
44
|
+
RESET
|
45
|
+
REVOKE
|
46
|
+
ROLLBACK
|
47
|
+
SAVEPOINT
|
48
|
+
SECURITY
|
49
|
+
SELECT
|
50
|
+
SELECT
|
51
|
+
SET
|
52
|
+
SHOW
|
53
|
+
START
|
54
|
+
TRUNCATE
|
55
|
+
UNLISTEN
|
56
|
+
UPDATE
|
57
|
+
VACUUM
|
58
|
+
VALUES
|
59
|
+
].freeze
|
60
|
+
|
61
|
+
# From: https://github.com/newrelic/newrelic-ruby-agent/blob/9787095d4b5b2d8fcaf2fdbd964ed07c731a8b6b/lib/new_relic/agent/database/obfuscation_helpers.rb#L9-L34
|
62
|
+
COMPONENTS_REGEX_MAP = {
|
63
|
+
single_quotes: /'(?:[^']|'')*?(?:\\'.*|'(?!'))/,
|
64
|
+
dollar_quotes: /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/,
|
65
|
+
uuids: /\{?(?:[0-9a-fA-F]\-*){32}\}?/,
|
66
|
+
numeric_literals: /-?\b(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/,
|
67
|
+
boolean_literals: /\b(?:true|false|null)\b/i,
|
68
|
+
comments: /(?:#|--).*?(?=\r|\n|$)/i,
|
69
|
+
multi_line_comments: %r{\/\*(?:[^\/]|\/[^*])*?(?:\*\/|\/\*.*)}
|
70
|
+
}.freeze
|
71
|
+
|
72
|
+
POSTGRES_COMPONENTS = %i[
|
73
|
+
single_quotes
|
74
|
+
dollar_quotes
|
75
|
+
uuids
|
76
|
+
numeric_literals
|
77
|
+
boolean_literals
|
78
|
+
comments
|
79
|
+
multi_line_comments
|
80
|
+
].freeze
|
81
|
+
|
82
|
+
UNMATCHED_PAIRS_REGEX = %r{'|\/\*|\*\/|\$(?!\?)}.freeze
|
83
|
+
|
84
|
+
# These are all alike in that they will have a SQL statement as the first parameter.
|
85
|
+
# That statement may possibly be parameterized, but we can still use it - the
|
86
|
+
# obfuscation code will just transform $1 -> $? in that case (which is fine enough).
|
87
|
+
EXEC_ISH_METHODS = %i[
|
88
|
+
exec
|
89
|
+
query
|
90
|
+
sync_exec
|
91
|
+
async_exec
|
92
|
+
exec_params
|
93
|
+
async_exec_params
|
94
|
+
sync_exec_params
|
95
|
+
].freeze
|
96
|
+
|
97
|
+
# The following methods all take a statement name as the first
|
98
|
+
# parameter, and a SQL statement as the second - and possibly
|
99
|
+
# further parameters after that. We can trace them all alike.
|
100
|
+
PREPARE_ISH_METHODS = %i[
|
101
|
+
prepare
|
102
|
+
async_prepare
|
103
|
+
sync_prepare
|
104
|
+
].freeze
|
105
|
+
|
106
|
+
# The following methods take a prepared statement name as their first
|
107
|
+
# parameter - everything after that is either potentially quite sensitive
|
108
|
+
# (an array of bind params) or not useful to us. We trace them all alike.
|
109
|
+
EXEC_PREPARED_ISH_METHODS = %i[
|
110
|
+
exec_prepared
|
111
|
+
async_exec_prepared
|
112
|
+
sync_exec_prepared
|
113
|
+
].freeze
|
114
|
+
|
115
|
+
EXEC_ISH_METHODS.each do |method|
|
116
|
+
define_method method do |*args|
|
117
|
+
span_name, attrs = span_attrs(:query, *args)
|
118
|
+
tracer.in_span(span_name, attributes: attrs, kind: :client) do
|
119
|
+
super(*args)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
PREPARE_ISH_METHODS.each do |method|
|
125
|
+
define_method method do |*args|
|
126
|
+
span_name, attrs = span_attrs(:prepare, *args)
|
127
|
+
tracer.in_span(span_name, attributes: attrs, kind: :client) do
|
128
|
+
super(*args)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
EXEC_PREPARED_ISH_METHODS.each do |method|
|
134
|
+
define_method method do |*args|
|
135
|
+
span_name, attrs = span_attrs(:execute, *args)
|
136
|
+
tracer.in_span(span_name, attributes: attrs, kind: :client) do
|
137
|
+
super(*args)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def config
|
143
|
+
EpsagonPostgresInstrumentation.instance.config
|
144
|
+
end
|
145
|
+
|
146
|
+
def tracer
|
147
|
+
EpsagonPostgresInstrumentation.instance.tracer
|
148
|
+
end
|
149
|
+
|
150
|
+
def lru_cache
|
151
|
+
# When SQL is being sanitized, we know that this cache will
|
152
|
+
# never be more than 50 entries * 2000 characters (so, presumably
|
153
|
+
# 100k bytes - or 97k). When not sanitizing SQL, then this cache
|
154
|
+
# could grow much larger - but the small cache size should otherwise
|
155
|
+
# help contain memory growth. The intended use here is to cache
|
156
|
+
# prepared SQL statements, so that we can attach a reasonable
|
157
|
+
# `db.sql.statement` value to spans when those prepared statements
|
158
|
+
# are executed later on.
|
159
|
+
@lru_cache ||= LruCache.new(50)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Rubocop is complaining about 19.31/18 for Metrics/AbcSize.
|
163
|
+
# But, getting that metric in line would force us over the
|
164
|
+
# module size limit! We can't win here unless we want to start
|
165
|
+
# abstracting things into a million pieces.
|
166
|
+
def span_attrs(kind, *args) # rubocop:disable Metrics/AbcSize
|
167
|
+
if kind == :query
|
168
|
+
operation = extract_operation(args[0])
|
169
|
+
sql = args[0]
|
170
|
+
else
|
171
|
+
statement_name = args[0]
|
172
|
+
|
173
|
+
if kind == :prepare
|
174
|
+
sql = args[1]
|
175
|
+
lru_cache[statement_name] = sql
|
176
|
+
operation = 'PREPARE'
|
177
|
+
else
|
178
|
+
sql = lru_cache[statement_name]
|
179
|
+
operation = 'EXECUTE'
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
attrs = { 'db.operation' => validated_operation(operation), 'db.postgresql.prepared_statement_name' => statement_name }
|
184
|
+
attrs['db.statement'] = sql if config[:epsagon][:metadata_only] == false
|
185
|
+
attrs['db.sql.table'] = table_name(sql)
|
186
|
+
attrs.reject! { |_, v| v.nil? }
|
187
|
+
|
188
|
+
[database_name, client_attributes.merge(attrs)]
|
189
|
+
end
|
190
|
+
|
191
|
+
def table_name(sql)
|
192
|
+
return '' if sql.nil?
|
193
|
+
|
194
|
+
parsed_query = PgQuery.parse(sql)
|
195
|
+
if parsed_query.tables.length == 0
|
196
|
+
''
|
197
|
+
else
|
198
|
+
parsed_query.tables[0]
|
199
|
+
end
|
200
|
+
rescue PgQuery::ParseError
|
201
|
+
''
|
202
|
+
end
|
203
|
+
|
204
|
+
def validated_operation(operation)
|
205
|
+
operation if PostgresExtension::SQL_COMMANDS.include?(operation)
|
206
|
+
end
|
207
|
+
|
208
|
+
def extract_operation(sql)
|
209
|
+
# From: https://github.com/open-telemetry/opentelemetry-js-contrib/blob/9244a08a8d014afe26b82b91cf86e407c2599d73/plugins/node/opentelemetry-instrumentation-pg/src/utils.ts#L35
|
210
|
+
sql.to_s.split[0].to_s.upcase
|
211
|
+
end
|
212
|
+
|
213
|
+
def generated_postgres_regex
|
214
|
+
@generated_postgres_regex ||= Regexp.union(PostgresExtension::POSTGRES_COMPONENTS.map { |component| PostgresExtension::COMPONENTS_REGEX_MAP[component] })
|
215
|
+
end
|
216
|
+
|
217
|
+
def database_name
|
218
|
+
conninfo_hash[:dbname]&.to_s
|
219
|
+
end
|
220
|
+
|
221
|
+
def client_attributes
|
222
|
+
attributes = {
|
223
|
+
'db.system' => 'postgresql',
|
224
|
+
'db.user' => conninfo_hash[:user]&.to_s,
|
225
|
+
'db.name' => database_name,
|
226
|
+
'net.peer.name' => conninfo_hash[:host]&.to_s
|
227
|
+
}
|
228
|
+
# attributes['peer.service'] = config[:peer_service] # if config[:peer_service]
|
229
|
+
|
230
|
+
attributes.merge(transport_attrs).reject { |_, v| v.nil? }
|
231
|
+
end
|
232
|
+
|
233
|
+
def transport_attrs
|
234
|
+
if conninfo_hash[:host]&.start_with?('/')
|
235
|
+
{ 'net.transport' => 'Unix' }
|
236
|
+
else
|
237
|
+
{
|
238
|
+
'net.transport' => 'IP.TCP',
|
239
|
+
'net.peer.ip' => conninfo_hash[:hostaddr]&.to_s,
|
240
|
+
'net.peer.port' => conninfo_hash[:port]&.to_s
|
241
|
+
}
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Copyright The OpenTelemetry Authors
|
247
|
+
#
|
248
|
+
# SPDX-License-Identifier: Apache-2.0
|
249
|
+
# A simple LRU cache for the postgres instrumentation.
|
250
|
+
class LruCache
|
251
|
+
# Rather than take a dependency on another gem, we implement a very, very basic
|
252
|
+
# LRU cache here. We can take advantage of the fact that Ruby hashes are ordered
|
253
|
+
# to always keep the recently-accessed keys at the top.
|
254
|
+
def initialize(size)
|
255
|
+
raise ArgumentError, 'Invalid size' if size < 1
|
256
|
+
|
257
|
+
@limit = size
|
258
|
+
@store = {}
|
259
|
+
end
|
260
|
+
|
261
|
+
def [](key)
|
262
|
+
# We need to check for the key explicitly, because `nil` is a valid hash value.
|
263
|
+
return unless @store.key?(key)
|
264
|
+
|
265
|
+
# Since the cache contains the item, we delete and re-insert into the hash.
|
266
|
+
# This guarantees that hash keys are ordered by access recency.
|
267
|
+
value = @store.delete(key)
|
268
|
+
@store[key] = value
|
269
|
+
|
270
|
+
value
|
271
|
+
end
|
272
|
+
|
273
|
+
def []=(key, value)
|
274
|
+
# We remove the value if it's already present, so that the hash keys remain ordered
|
275
|
+
# by access recency.
|
276
|
+
@store.delete(key)
|
277
|
+
@store[key] = value
|
278
|
+
@store.shift if @store.length > @limit
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
#
|
283
|
+
# EpsagonPostgresInstrumentation
|
284
|
+
# Installs the Instrumentation on the PG::Connection class
|
285
|
+
#
|
286
|
+
class EpsagonPostgresInstrumentation < OpenTelemetry::Instrumentation::Base
|
287
|
+
install do |_config|
|
288
|
+
::PG::Connection.prepend(PostgresExtension)
|
289
|
+
end
|
290
|
+
|
291
|
+
present do
|
292
|
+
defined?(::PG)
|
293
|
+
end
|
294
|
+
end
|
File without changes
|
data/lib/util.rb
CHANGED
@@ -30,4 +30,13 @@ module Util
|
|
30
30
|
value
|
31
31
|
end
|
32
32
|
end
|
33
|
+
|
34
|
+
def self.redis_default_url
|
35
|
+
@@redis_default_url ||= "#{Redis::Client::DEFAULTS[:scheme]}://#{Redis::Client::DEFAULTS[:host]}:#{Redis::Client::DEFAULTS[:port]}/#{Redis::Client::DEFAULTS[:db]}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.untraced(&block)
|
39
|
+
OpenTelemetry::Trace.with_span(OpenTelemetry::Trace::Span.new, &block)
|
40
|
+
end
|
41
|
+
|
33
42
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: epsagon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.28
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Epsagon
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-06-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: opentelemetry-api
|
@@ -80,32 +80,50 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 0.11.1
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pg_query
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.0'
|
83
97
|
description: 'Epsagon provides tracing to Ruby applications for the collection of
|
84
98
|
distributed tracing and performance metrics to simplify complex architectures, eliminate
|
85
99
|
manual work, visualize and correlate data to identify and fix problems fast.
|
86
100
|
|
87
|
-
|
101
|
+
'
|
88
102
|
email: info@epsagon.com
|
89
103
|
executables: []
|
90
104
|
extensions: []
|
91
105
|
extra_rdoc_files: []
|
92
106
|
files:
|
107
|
+
- lib/arn_parser.rb
|
93
108
|
- lib/epsagon.rb
|
94
109
|
- lib/epsagon_constants.rb
|
110
|
+
- lib/exporter_extension.rb
|
95
111
|
- lib/instrumentation/aws_sdk.rb
|
96
112
|
- lib/instrumentation/aws_sdk_plugin.rb
|
97
113
|
- lib/instrumentation/epsagon_faraday_middleware.rb
|
98
114
|
- lib/instrumentation/epsagon_rails_middleware.rb
|
99
115
|
- lib/instrumentation/faraday.rb
|
100
116
|
- lib/instrumentation/net_http.rb
|
117
|
+
- lib/instrumentation/postgres.rb
|
101
118
|
- lib/instrumentation/rails.rb
|
102
119
|
- lib/instrumentation/sinatra.rb
|
120
|
+
- lib/instrumentation/version.rb
|
103
121
|
- lib/util.rb
|
104
122
|
homepage: https://github.com/epsagon/epsagon-ruby
|
105
123
|
licenses:
|
106
124
|
- MIT
|
107
125
|
metadata: {}
|
108
|
-
post_install_message:
|
126
|
+
post_install_message:
|
109
127
|
rdoc_options: []
|
110
128
|
require_paths:
|
111
129
|
- lib
|
@@ -120,8 +138,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
120
138
|
- !ruby/object:Gem::Version
|
121
139
|
version: '0'
|
122
140
|
requirements: []
|
123
|
-
rubygems_version: 3.
|
124
|
-
signing_key:
|
141
|
+
rubygems_version: 3.0.3
|
142
|
+
signing_key:
|
125
143
|
specification_version: 4
|
126
144
|
summary: Epsagon provides tracing to Ruby applications for the collection of distributed
|
127
145
|
tracing and performance metrics.
|