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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f90417afae2709a9a4dbc88f2edab85cea49ff77577048808401f981cba0a96
4
- data.tar.gz: '0811d976ea61bce684d2da4322fb2493b37b58af0c269b1ff785cdede6b5fad2'
3
+ metadata.gz: e4dd6a90b916b237e688e45551af0324cebf6dc8288c5be00c2c360604748aa7
4
+ data.tar.gz: e70b552116e3a68515e6363352527ba0e1a438a1b8f26d9882358c61e7f2f5f7
5
5
  SHA512:
6
- metadata.gz: 560b2ea93192ed4502f261433728983544f3696378c846df19734602187335f6a155b52bbbaf7c424aca161727b09b37477b7f5a25b2d0711045a9a6c61b40dd
7
- data.tar.gz: 5f3a848235508e9adc51555d6ebf2c107dcedde0aba4a0da37d961e37fa143064968cceca46170e9bdd92fc3bcbd8f980f4b7cbf9d7503eb2b77659e98d976fe
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 |span|
162
- span.add_event('created_at', timestamp: msg['created_at'])
163
- span.add_event('enqueued_at', timestamp: msg['enqueued_at'])
164
- yield
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
@@ -1,3 +1,3 @@
1
1
  module EpsagonConstants
2
- VERSION = '0.0.24'
2
+ VERSION = '0.0.25'
3
3
  end
@@ -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
- tracer.in_span('', kind: :client) do |span|
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
- span.set_attribute('aws.service', context.client.class.to_s.split('::')[1].downcase)
25
- span.set_attribute('aws.operation', context.operation.name)
26
- span.set_attribute('aws.region', context.client.config.region)
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
- '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
- })
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.24
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-05-12 00:00:00.000000000 Z
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