epsagon 0.0.24 → 0.0.25
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/epsagon.rb +30 -17
- 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 +1 -1
- data/lib/instrumentation/net_http.rb +2 -0
- 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: e4dd6a90b916b237e688e45551af0324cebf6dc8288c5be00c2c360604748aa7
|
4
|
+
data.tar.gz: e70b552116e3a68515e6363352527ba0e1a438a1b8f26d9882358c61e7f2f5f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '048ca5df1c956861b25634b497ceb8b1e6bcb45ff171a14e4c61bb1d1ea8731b5cf87237a158debe2ff146b81ba72445c1050bab2ba875596624cef82b1feec1'
|
7
|
+
data.tar.gz: 7474c11f77a5f24da6a23e7c825e1f4cf1e2ef0111cabd0ece1c68322a3e5cc8345e499618796f41de6484b2cdfb98c95abc7d1eeca3c13b2abb7a63d00a60b3
|
data/lib/epsagon.rb
CHANGED
@@ -14,26 +14,29 @@ require_relative 'instrumentation/aws_sdk'
|
|
14
14
|
require_relative 'instrumentation/rails'
|
15
15
|
require_relative 'util'
|
16
16
|
require_relative 'epsagon_constants'
|
17
|
+
require_relative 'exporter_extension'
|
17
18
|
|
18
19
|
Bundler.require
|
19
20
|
|
20
21
|
# Epsagon tracing main entry point
|
21
22
|
module Epsagon
|
22
|
-
|
23
23
|
DEFAULT_BACKEND = 'opentelemetry.tc.epsagon.com:443/traces'
|
24
|
+
DEFAULT_IGNORE_DOMAINS = ['newrelic.com']
|
24
25
|
|
25
|
-
@@epsagon_config = {
|
26
|
-
metadata_only: ENV['EPSAGON_METADATA']&.to_s&.downcase != 'false',
|
27
|
-
debug: ENV['EPSAGON_DEBUG']&.to_s&.downcase == 'true',
|
28
|
-
token: ENV['EPSAGON_TOKEN'],
|
29
|
-
app_name: ENV['EPSAGON_APP_NAME'],
|
30
|
-
max_attribute_size: ENV['EPSAGON_MAX_ATTRIBUTE_SIZE'] || 5000,
|
31
|
-
backend: ENV['EPSAGON_BACKEND'] || DEFAULT_BACKEND
|
32
|
-
}
|
26
|
+
@@epsagon_config = {}
|
33
27
|
|
34
28
|
module_function
|
35
29
|
|
36
30
|
def init(**args)
|
31
|
+
@@epsagon_config = {
|
32
|
+
metadata_only: ENV['EPSAGON_METADATA']&.to_s&.downcase != 'false',
|
33
|
+
debug: ENV['EPSAGON_DEBUG']&.to_s&.downcase == 'true',
|
34
|
+
token: ENV['EPSAGON_TOKEN'] || '',
|
35
|
+
app_name: ENV['EPSAGON_APP_NAME'] || '',
|
36
|
+
max_attribute_size: ENV['EPSAGON_MAX_ATTRIBUTE_SIZE'] || 5000,
|
37
|
+
backend: ENV['EPSAGON_BACKEND'] || DEFAULT_BACKEND,
|
38
|
+
ignore_domains: ENV['EPSAGON_IGNORE_DOMAINS'] || DEFAULT_IGNORE_DOMAINS
|
39
|
+
}
|
37
40
|
@@epsagon_config.merge!(args)
|
38
41
|
OpenTelemetry::SDK.configure
|
39
42
|
end
|
@@ -46,9 +49,10 @@ module Epsagon
|
|
46
49
|
|
47
50
|
def epsagon_confs(configurator)
|
48
51
|
configurator.resource = OpenTelemetry::SDK::Resources::Resource.telemetry_sdk.merge(
|
49
|
-
OpenTelemetry::SDK::Resources::Resource.create({
|
52
|
+
OpenTelemetry::SDK::Resources::Resource.create({
|
50
53
|
'application' => @@epsagon_config[:app_name],
|
51
|
-
'epsagon.version' => EpsagonConstants::VERSION
|
54
|
+
'epsagon.version' => EpsagonConstants::VERSION,
|
55
|
+
'epsagon.metadata_only' => @@epsagon_config[:metadata_only]
|
52
56
|
})
|
53
57
|
)
|
54
58
|
configurator.use 'EpsagonSinatraInstrumentation', { epsagon: @@epsagon_config }
|
@@ -97,11 +101,10 @@ module SpanExtension
|
|
97
101
|
def initialize(*args)
|
98
102
|
super(*args)
|
99
103
|
if @attributes
|
100
|
-
@attributes = Hash[@attributes.map { |k,v|
|
104
|
+
@attributes = Hash[@attributes.select {|k,v| not BLANKS.include? v}.map { |k,v|
|
101
105
|
[k, Util.trim_attr(v, Epsagon.get_config[:max_attribute_size])]
|
102
106
|
}]
|
103
107
|
end
|
104
|
-
|
105
108
|
end
|
106
109
|
end
|
107
110
|
|
@@ -148,6 +151,11 @@ module SidekiqServerMiddlewareExtension
|
|
148
151
|
'messaging.destination_kind' => 'queue',
|
149
152
|
'messaging.sidekiq.redis_url' => Sidekiq.options['url'] || Util.redis_default_url
|
150
153
|
}
|
154
|
+
runner_attributes = {
|
155
|
+
'type' => 'sidekiq_worker',
|
156
|
+
'messaging.sidekiq.redis_url' => Sidekiq.options['url'] || Util.redis_default_url,
|
157
|
+
|
158
|
+
}
|
151
159
|
unless config[:metadata_only]
|
152
160
|
attributes.merge!({
|
153
161
|
'messaging.sidekiq.args' => JSON.dump(msg['args'])
|
@@ -158,10 +166,15 @@ module SidekiqServerMiddlewareExtension
|
|
158
166
|
attributes: attributes,
|
159
167
|
with_parent: parent_context,
|
160
168
|
kind: :consumer
|
161
|
-
) do |
|
162
|
-
|
163
|
-
|
164
|
-
|
169
|
+
) do |trigger_span|
|
170
|
+
trigger_span.add_event('created_at', timestamp: msg['created_at'])
|
171
|
+
trigger_span.add_event('enqueued_at', timestamp: msg['enqueued_at'])
|
172
|
+
tracer.in_span(msg['wrapped']&.to_s || msg['class'],
|
173
|
+
attributes: runner_attributes,
|
174
|
+
kind: :consumer
|
175
|
+
) do |runner_span|
|
176
|
+
yield
|
177
|
+
end
|
165
178
|
end
|
166
179
|
end
|
167
180
|
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
|
@@ -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
|
|
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.25
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Epsagon
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-06-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: opentelemetry-api
|
@@ -92,6 +92,7 @@ extra_rdoc_files: []
|
|
92
92
|
files:
|
93
93
|
- lib/epsagon.rb
|
94
94
|
- lib/epsagon_constants.rb
|
95
|
+
- lib/exporter_extension.rb
|
95
96
|
- lib/instrumentation/aws_sdk.rb
|
96
97
|
- lib/instrumentation/aws_sdk_plugin.rb
|
97
98
|
- lib/instrumentation/epsagon_faraday_middleware.rb
|