epsagon 0.0.24 → 0.0.25
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/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
|