elastic-apm 4.5.1 → 4.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/.exclude.yml +193 -0
  3. data/.ci/{.jenkins_framework.yml → .framework.yml} +3 -2
  4. data/.ci/.main_framework.yml +4 -0
  5. data/.ci/.ruby.yml +10 -0
  6. data/.ci/docker/jruby/11-jdk/Dockerfile +8 -3
  7. data/.ci/docker/jruby/12-jdk/Dockerfile +5 -2
  8. data/.ci/docker/jruby/13-jdk/Dockerfile +5 -2
  9. data/.ci/docker/jruby/7-jdk/Dockerfile +6 -3
  10. data/.ci/docker/jruby/8-jdk/Dockerfile +8 -3
  11. data/.ci/docker/jruby/README.md +1 -1
  12. data/.ci/docker/jruby/run.sh +33 -9
  13. data/.ci/docker/jruby/test.sh +17 -2
  14. data/.ci/scripts/bench.sh +52 -0
  15. data/.ci/scripts/install-build-system.sh +5 -0
  16. data/.ci/snapshoty.yml +33 -0
  17. data/.ci/updatecli.d/update-gherkin-specs.yml +126 -0
  18. data/.ci/updatecli.d/update-json-specs.yml +130 -0
  19. data/.ci/updatecli.d/update-specs.yml +118 -0
  20. data/.github/dependabot.yml +17 -1
  21. data/.github/workflows/README.md +58 -0
  22. data/.github/workflows/ci-docs.yml +20 -0
  23. data/.github/workflows/ci.yml +70 -0
  24. data/.github/workflows/coverage-reporter.yml +34 -0
  25. data/.github/workflows/microbenchmark.yml +48 -0
  26. data/.github/workflows/opentelemetry.yml +27 -0
  27. data/.github/workflows/release.yml +71 -0
  28. data/.github/workflows/run-matrix.yml +56 -0
  29. data/.github/workflows/snapshoty.yml +35 -0
  30. data/.github/workflows/test-reporter.yml +24 -0
  31. data/.github/workflows/updatecli.yml +28 -0
  32. data/.pre-commit-config.yaml +0 -2
  33. data/CHANGELOG.asciidoc +73 -1
  34. data/CONTRIBUTING.md +1 -1
  35. data/Gemfile +28 -8
  36. data/README.md +1 -1
  37. data/Rakefile +2 -2
  38. data/bench/report.rb +1 -1
  39. data/bench/rubyprof.rb +1 -0
  40. data/bin/run-tests +10 -1
  41. data/docker-compose.yml +10 -1
  42. data/docs/configuration.asciidoc +12 -9
  43. data/docs/index.asciidoc +2 -2
  44. data/docs/{log-correlation.asciidoc → logs.asciidoc} +29 -2
  45. data/docs/opentracing.asciidoc +1 -1
  46. data/docs/redirects.asciidoc +9 -0
  47. data/elastic-apm.gemspec +3 -2
  48. data/lib/elastic_apm/central_config.rb +5 -0
  49. data/lib/elastic_apm/config/server_info.rb +50 -0
  50. data/lib/elastic_apm/config.rb +25 -4
  51. data/lib/elastic_apm/context_builder.rb +6 -3
  52. data/lib/elastic_apm/error.rb +2 -1
  53. data/lib/elastic_apm/error_builder.rb +1 -0
  54. data/lib/elastic_apm/instrumenter.rb +4 -2
  55. data/lib/elastic_apm/metadata/cloud_info.rb +9 -7
  56. data/lib/elastic_apm/metadata/system_info/container_info.rb +4 -3
  57. data/lib/elastic_apm/metadata/system_info.rb +1 -1
  58. data/lib/elastic_apm/metrics.rb +24 -20
  59. data/lib/elastic_apm/span/context/links.rb +32 -0
  60. data/lib/elastic_apm/span/context/service.rb +55 -0
  61. data/lib/elastic_apm/span/context.rb +19 -3
  62. data/lib/elastic_apm/span.rb +3 -0
  63. data/lib/elastic_apm/span_helpers.rb +8 -8
  64. data/lib/elastic_apm/spies/action_dispatch.rb +11 -3
  65. data/lib/elastic_apm/spies/faraday.rb +20 -4
  66. data/lib/elastic_apm/spies/racecar.rb +77 -0
  67. data/lib/elastic_apm/spies/redis.rb +1 -1
  68. data/lib/elastic_apm/spies/sequel.rb +9 -0
  69. data/lib/elastic_apm/spies/sqs.rb +1 -0
  70. data/lib/elastic_apm/trace_context/tracestate.rb +4 -2
  71. data/lib/elastic_apm/trace_context.rb +1 -1
  72. data/lib/elastic_apm/transport/connection/http.rb +9 -3
  73. data/lib/elastic_apm/transport/serializers/span_serializer.rb +25 -0
  74. data/lib/elastic_apm/version.rb +1 -1
  75. data/lib/elastic_apm.rb +1 -0
  76. metadata +46 -17
  77. data/.ci/.jenkins_exclude.yml +0 -134
  78. data/.ci/.jenkins_main_framework.yml +0 -4
  79. data/.ci/.jenkins_ruby.yml +0 -10
  80. data/.ci/Jenkinsfile +0 -441
  81. data/.ci/jobs/apm-agent-ruby-downstream.yml +0 -38
  82. data/.ci/jobs/apm-agent-ruby-linting-mbp.yml +0 -39
  83. data/.ci/jobs/apm-agent-ruby-mbp.yml +0 -43
  84. data/.ci/jobs/apm-agent-ruby.yml +0 -4
  85. data/.ci/jobs/defaults.yml +0 -24
  86. data/.ci/linting.groovy +0 -32
  87. data/.ci/packer_cache.sh +0 -16
  88. /data/.ci/{.jenkins_codecov.yml → .codecov.yml} +0 -0
data/elastic-apm.gemspec CHANGED
@@ -22,8 +22,8 @@ require 'elastic_apm/version'
22
22
  Gem::Specification.new do |spec|
23
23
  spec.name = 'elastic-apm'
24
24
  spec.version = ElasticAPM::VERSION
25
- spec.authors = ['Mikkel Malmberg']
26
- spec.email = ['mikkel@elastic.co']
25
+ spec.authors = ['Mikkel Malmberg', 'Emily Stolfo']
26
+ spec.email = ['info@elastic.co']
27
27
 
28
28
  spec.summary = 'The official Elastic APM agent for Ruby'
29
29
  spec.homepage = 'https://github.com/elastic/apm-agent-ruby'
@@ -37,6 +37,7 @@ Gem::Specification.new do |spec|
37
37
 
38
38
  spec.add_dependency('concurrent-ruby', '~> 1.0')
39
39
  spec.add_dependency('http', '>= 3.0')
40
+ spec.add_runtime_dependency('ruby2_keywords')
40
41
 
41
42
  spec.require_paths = ['lib']
42
43
  end
@@ -181,6 +181,11 @@ module ElasticAPM
181
181
  DEFAULT_MAX_AGE
182
182
  end
183
183
 
184
+ if seconds < 5
185
+ debug "Next fetch is too low (#{seconds}s) - increasing to default"
186
+ seconds = 5
187
+ end
188
+
184
189
  @scheduled_task =
185
190
  Concurrent::ScheduledTask
186
191
  .execute(seconds) { fetch_and_apply_config }
@@ -0,0 +1,50 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ module ElasticAPM
21
+ class Config
22
+ # @api private
23
+ class ServerInfo
24
+ attr_reader :payload, :config, :http
25
+
26
+ VERSION_8_0 = '8.0'
27
+ VERSION_0 = '0'
28
+
29
+ def initialize(config)
30
+ @config = config
31
+ @http = Transport::Connection::Http.new(config)
32
+ end
33
+
34
+ def execute
35
+ resp = http.get(config.server_url)
36
+ @payload = JSON.parse(resp.body)
37
+ rescue
38
+ @payload = { "version" => VERSION_0 }
39
+ end
40
+
41
+ def version
42
+ @version ||= begin
43
+ execute
44
+ payload["version"] ? payload["version"].to_s : VERSION_0
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -24,14 +24,17 @@ require 'elastic_apm/config/options'
24
24
  require 'elastic_apm/config/round_float'
25
25
  require 'elastic_apm/config/regexp_list'
26
26
  require 'elastic_apm/config/wildcard_pattern_list'
27
+ require 'elastic_apm/deprecations'
28
+ require 'elastic_apm/config/server_info'
27
29
 
28
30
  module ElasticAPM
29
31
  # @api private
30
32
  class Config
31
33
  extend Options
34
+ extend Deprecations
32
35
 
33
36
  SANITIZE_FIELD_NAMES_DEFAULT =
34
- %w[password passwd pwd secret *key *token* *session* *credit* *card* authorization set-cookie].freeze
37
+ %w[password passwd pwd secret *key *token* *session* *credit* *card* *auth* set-cookie].freeze
35
38
 
36
39
  # rubocop:disable Layout/LineLength, Layout/ExtraSpacing
37
40
  option :config_file, type: :string, default: 'config/elastic_apm.yml'
@@ -70,7 +73,7 @@ module ElasticAPM
70
73
  option :ignore_url_patterns, type: :list, default: [], converter: RegexpList.new
71
74
  option :instrument, type: :bool, default: true
72
75
  option :instrumented_rake_tasks, type: :list, default: []
73
- option :log_ecs_formatting, type: :string, default: 'off'
76
+ option :log_ecs_reformatting, type: :string, default: 'off'
74
77
  option :log_level, type: :int, default: Logger::INFO, converter: LogLevelMap.new
75
78
  option :log_path, type: :string
76
79
  option :metrics_interval, type: :int, default: '30s', converter: Duration.new
@@ -98,6 +101,16 @@ module ElasticAPM
98
101
  option :use_elastic_traceparent_header, type: :bool, default: true
99
102
  option :verify_server_cert, type: :bool, default: true
100
103
 
104
+ def log_ecs_formatting
105
+ log_ecs_reformatting
106
+ end
107
+
108
+ def log_ecs_formatting=(value)
109
+ @options[:log_ecs_reformatting].set(value)
110
+ end
111
+
112
+ deprecate :log_ecs_formatting, :log_ecs_reformatting
113
+
101
114
  # rubocop:enable Layout/LineLength, Layout/ExtraSpacing
102
115
  def initialize(options = {})
103
116
  @options = load_schema
@@ -116,7 +129,9 @@ module ElasticAPM
116
129
 
117
130
  yield self if block_given?
118
131
 
119
- self.logger ||= build_logger
132
+ if self.logger.nil? || self.log_path
133
+ self.logger = build_logger
134
+ end
120
135
 
121
136
  @__view_paths ||= []
122
137
  @__root_path ||= Dir.pwd
@@ -144,6 +159,7 @@ module ElasticAPM
144
159
  mongo
145
160
  net_http
146
161
  rake
162
+ racecar
147
163
  redis
148
164
  resque
149
165
  s3
@@ -229,6 +245,10 @@ module ElasticAPM
229
245
  super.split.first + '>'
230
246
  end
231
247
 
248
+ def version
249
+ @version ||= ServerInfo.new(self).version
250
+ end
251
+
232
252
  private
233
253
 
234
254
  def load_config_file
@@ -247,7 +267,7 @@ module ElasticAPM
247
267
  end
248
268
 
249
269
  def build_logger
250
- if self.log_ecs_formatting == 'override'
270
+ if self.log_ecs_reformatting == 'override'
251
271
  begin
252
272
  return build_ecs_logger
253
273
  rescue LoadError
@@ -294,6 +314,7 @@ module ElasticAPM
294
314
  self.framework_name ||= 'Ruby on Rails'
295
315
  self.framework_version ||= ::Rails::VERSION::STRING
296
316
  self.logger ||= ::Rails.logger
317
+ self.log_level ||= ::Rails.logger.log_level
297
318
 
298
319
  self.__root_path = ::Rails.root.to_s
299
320
  self.__view_paths = app.config.paths['app/views'].existent +
@@ -44,7 +44,7 @@ module ElasticAPM
44
44
  request = context.request
45
45
 
46
46
  request.socket = Context::Request::Socket.new(req)
47
- request.http_version = build_http_version rack_env
47
+ request.http_version = build_http_version req
48
48
  request.method = req.request_method
49
49
  request.url = Context::Request::Url.new(req)
50
50
 
@@ -55,6 +55,9 @@ module ElasticAPM
55
55
  request.env = env if config.capture_env?
56
56
 
57
57
  request.cookies = req.cookies.dup
58
+ unless request.cookies.empty?
59
+ request.headers['Cookie'] = SKIPPED if request.headers.has_key?('Cookie')
60
+ end
58
61
 
59
62
  context
60
63
  end
@@ -104,8 +107,8 @@ module ElasticAPM
104
107
  key.gsub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
105
108
  end
106
109
 
107
- def build_http_version(rack_env)
108
- return unless (http_version = rack_env['HTTP_VERSION'])
110
+ def build_http_version(req)
111
+ return unless (http_version = req.env['HTTP_VERSION'])
109
112
  http_version.gsub(%r{HTTP/}, '')
110
113
  end
111
114
  end
@@ -33,7 +33,7 @@ module ElasticAPM
33
33
  end
34
34
 
35
35
  attr_accessor :id, :culprit, :exception, :log, :transaction_id,
36
- :transaction, :context, :parent_id, :trace_id
36
+ :transaction_name, :transaction, :context, :parent_id, :trace_id
37
37
  attr_reader :timestamp
38
38
 
39
39
  def inspect
@@ -41,6 +41,7 @@ module ElasticAPM
41
41
  " culprit:#{culprit}" \
42
42
  " timestamp:#{timestamp}" \
43
43
  " transaction_id:#{transaction_id}" \
44
+ " transaction_name:#{transaction_name}" \
44
45
  " trace_id:#{trace_id}" \
45
46
  " exception:#{exception.inspect}" \
46
47
  '>'
@@ -74,6 +74,7 @@ module ElasticAPM
74
74
  return unless transaction
75
75
 
76
76
  error.transaction_id = transaction.id
77
+ error.transaction_name = transaction.name
77
78
  error.transaction = {
78
79
  sampled: transaction.sampled?,
79
80
  type: transaction.type
@@ -124,7 +124,7 @@ module ElasticAPM
124
124
  sample_rate = trace_context.tracestate.sample_rate
125
125
  else
126
126
  sampled = random_sample?(config)
127
- sample_rate = config.transaction_sample_rate
127
+ sample_rate = sampled ? config.transaction_sample_rate : 0
128
128
  end
129
129
 
130
130
  transaction =
@@ -150,7 +150,9 @@ module ElasticAPM
150
150
 
151
151
  transaction.done result
152
152
 
153
- enqueue.call transaction
153
+ if transaction.sampled? || @config.version < Config::ServerInfo::VERSION_8_0
154
+ enqueue.call transaction
155
+ end
154
156
 
155
157
  update_transaction_metrics(transaction)
156
158
 
@@ -94,16 +94,18 @@ module ElasticAPM
94
94
  return unless resp.status == 200
95
95
  return unless (metadata = JSON.parse(resp.body.to_s))
96
96
 
97
- zone = metadata["instance"]["zone"]&.split("/")&.at(-1)
98
-
99
97
  self.provider = "gcp"
100
- self.instance_id = metadata["instance"]["id"]
101
- self.instance_name = metadata["instance"]["name"]
102
- self.project_id = metadata["project"]["numericProjectId"]
103
- self.project_name = metadata["project"]["projectId"]
98
+ self.project_id = metadata["project"]["projectId"]
99
+
100
+ return unless metadata['instance']
101
+
102
+ zone = metadata["instance"]["zone"]&.split("/")&.at(-1)
104
103
  self.availability_zone = zone
105
104
  self.region = zone.split("-")[0..-2].join("-")
106
- self.machine_type = metadata["instance"]["machineType"]
105
+
106
+ self.instance_id = metadata["instance"]["id"].to_s
107
+ self.instance_name = metadata["instance"]["name"]
108
+ self.machine_type = metadata["instance"]["machineType"]&.split("/")&.at(-1)
107
109
  rescue HTTP::TimeoutError, HTTP::ConnectionError
108
110
  nil
109
111
  end
@@ -33,14 +33,15 @@ module ElasticAPM
33
33
 
34
34
  attr_reader :cgroup_path
35
35
 
36
- def read!
36
+ def read!(hostname)
37
37
  read_from_cgroup!
38
+ self.kubernetes_pod_name = hostname if kubernetes_pod_uid
38
39
  read_from_env!
39
40
  self
40
41
  end
41
42
 
42
- def self.read!
43
- new.read!
43
+ def self.read!(hostname)
44
+ new.read!(hostname)
44
45
  end
45
46
 
46
47
  def container
@@ -29,7 +29,7 @@ module ElasticAPM
29
29
  @architecture = gem_platform.cpu
30
30
  @platform = gem_platform.os
31
31
 
32
- container_info = ContainerInfo.read!
32
+ container_info = ContainerInfo.read!(@detected_hostname)
33
33
  @container = container_info.container
34
34
  @kubernetes = container_info.kubernetes
35
35
  end
@@ -50,27 +50,9 @@ module ElasticAPM
50
50
  return
51
51
  end
52
52
 
53
- debug 'Starting metrics'
54
-
55
- # Only set the @sets once, in case we stop
56
- # and start again.
57
- if @sets.nil?
58
- sets = {
59
- system: CpuMemSet,
60
- vm: VMSet,
61
- breakdown: BreakdownSet,
62
- transaction: TransactionSet
63
- }
64
- if defined?(JVMSet)
65
- debug "Enabling JVM metrics collection"
66
- sets[:jvm] = JVMSet
67
- end
53
+ define_sets
68
54
 
69
- @sets = sets.each_with_object({}) do |(key, kls), _sets_|
70
- debug "Adding metrics collector '#{kls}'"
71
- _sets_[key] = kls.new(config)
72
- end
73
- end
55
+ debug 'Starting metrics'
74
56
 
75
57
  @timer_task = Concurrent::TimerTask.execute(
76
58
  run_now: true,
@@ -135,6 +117,28 @@ module ElasticAPM
135
117
  arr.concat(samples)
136
118
  end
137
119
  end
120
+
121
+ def define_sets
122
+ # Only set the @sets once, in case we stop
123
+ # and start again.
124
+ return unless @sets.nil?
125
+
126
+ sets = {
127
+ system: CpuMemSet,
128
+ vm: VMSet,
129
+ breakdown: BreakdownSet,
130
+ transaction: TransactionSet
131
+ }
132
+ if defined?(JVMSet)
133
+ debug "Enabling JVM metrics collection"
134
+ sets[:jvm] = JVMSet
135
+ end
136
+
137
+ @sets = sets.each_with_object({}) do |(key, kls), _sets_|
138
+ debug "Adding metrics collector '#{kls}'"
139
+ _sets_[key] = kls.new(config)
140
+ end
141
+ end
138
142
  end
139
143
  end
140
144
  end
@@ -0,0 +1,32 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ module ElasticAPM
21
+ class Span
22
+ class Context
23
+ # @api private
24
+ class Links < Array
25
+ # @api private
26
+ class Link < Struct.new(:trace_id, :span_id)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,55 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ module ElasticAPM
21
+ class Span
22
+ class Context
23
+ # @api private
24
+ class Service
25
+ include Fields
26
+
27
+ field :target
28
+
29
+ # @api private
30
+ class Target
31
+ include Fields
32
+
33
+ field :name, default: ''
34
+ field :type, default: ''
35
+ end
36
+
37
+ def initialize(target: nil, **attrs)
38
+ super(**attrs)
39
+
40
+ self.target = build_target(target)
41
+ end
42
+
43
+ private
44
+
45
+ def build_target(target = nil)
46
+ return Target.new unless target
47
+ return target if target.is_a?(Target)
48
+
49
+ Target.new(**target)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
@@ -27,7 +27,9 @@ module ElasticAPM
27
27
  http: nil,
28
28
  labels: {},
29
29
  sync: nil,
30
- message: nil
30
+ message: nil,
31
+ service: nil,
32
+ links: nil
31
33
  )
32
34
  @sync = sync
33
35
  @db = db && Db.new(**db)
@@ -43,6 +45,16 @@ module ElasticAPM
43
45
  when Hash then Message.new(**message)
44
46
  end
45
47
  @labels = labels
48
+ @service =
49
+ case service
50
+ when Service then service
51
+ when Hash then Service.new(**service)
52
+ end
53
+ @links =
54
+ case links
55
+ when Links then links
56
+ when Array then Links.new(links)
57
+ end
46
58
  end
47
59
 
48
60
  attr_reader(
@@ -50,10 +62,11 @@ module ElasticAPM
50
62
  :http,
51
63
  :labels,
52
64
  :sync,
53
- :message
65
+ :message,
66
+ :links
54
67
  )
55
68
 
56
- attr_accessor :destination
69
+ attr_accessor :destination, :service
57
70
  end
58
71
  end
59
72
  end
@@ -62,3 +75,6 @@ require 'elastic_apm/span/context/db'
62
75
  require 'elastic_apm/span/context/http'
63
76
  require 'elastic_apm/span/context/destination'
64
77
  require 'elastic_apm/span/context/message'
78
+ require 'elastic_apm/span/context/service'
79
+ require 'elastic_apm/span/context/links'
80
+
@@ -145,6 +145,9 @@ module ElasticAPM
145
145
  service: service,
146
146
  cloud: cloud
147
147
  )
148
+ context.service = Span::Context::Service.new(
149
+ target: Span::Context::Service::Target.new(name: context.destination.service.name, type: context.destination.service.type )
150
+ )
148
151
  end
149
152
 
150
153
  def inspect
@@ -22,30 +22,30 @@ module ElasticAPM
22
22
  module SpanHelpers
23
23
  # @api private
24
24
  module ClassMethods
25
- def span_class_method(method, name = nil, type = nil)
26
- __span_method_on(singleton_class, method, name, type)
25
+ def span_class_method(method, name = nil, type = nil, **kwargs)
26
+ __span_method_on(singleton_class, method, name, type, **kwargs)
27
27
  end
28
28
 
29
- def span_method(method, name = nil, type = nil)
30
- __span_method_on(self, method, name, type)
29
+ def span_method(method, name = nil, type = nil, **kwargs)
30
+ __span_method_on(self, method, name, type, **kwargs)
31
31
  end
32
32
 
33
33
  private
34
34
 
35
- def __span_method_on(klass, method, name = nil, type = nil)
35
+ def __span_method_on(klass, method, name = nil, type = nil, **kwargs)
36
36
  name ||= method.to_s
37
37
  type ||= Span::DEFAULT_TYPE
38
38
 
39
39
  klass.prepend(Module.new do
40
- define_method(method) do |*args, &block|
40
+ ruby2_keywords(define_method(method) do |*args, &block|
41
41
  unless ElasticAPM.current_transaction
42
42
  return super(*args, &block)
43
43
  end
44
44
 
45
- ElasticAPM.with_span name.to_s, type.to_s do
45
+ ElasticAPM.with_span name.to_s, type.to_s, **kwargs do
46
46
  super(*args, &block)
47
47
  end
48
- end
48
+ end)
49
49
  end)
50
50
  end
51
51
  end
@@ -24,11 +24,19 @@ module ElasticAPM
24
24
  class ActionDispatchSpy
25
25
  # @api private
26
26
  module Ext
27
- def render_exception(env, exception)
28
- context = ElasticAPM.build_context(rack_env: env, for_type: :error)
27
+ def render_exception(request, exception_or_wrapper)
28
+ context = ElasticAPM.build_context(
29
+ rack_env: request, for_type: :error
30
+ )
31
+ exception =
32
+ if exception_or_wrapper.is_a?(ActionDispatch::ExceptionWrapper)
33
+ exception_or_wrapper.exception
34
+ else
35
+ exception_or_wrapper
36
+ end
29
37
  ElasticAPM.report(exception, context: context, handled: false)
30
38
 
31
- super(env, exception)
39
+ super(request, exception_or_wrapper)
32
40
  end
33
41
  end
34
42
 
@@ -103,10 +103,26 @@ module ElasticAPM
103
103
  ElasticAPM::Spies.without_net_http do
104
104
  trace_context = span&.trace_context || transaction.trace_context
105
105
 
106
- result = super(method, url, body, headers) do |req|
107
- trace_context.apply_headers { |k, v| req[k] = v }
108
-
109
- yield req if block
106
+ begin
107
+ result = super(method, url, body, headers) do |req|
108
+ trace_context.apply_headers { |k, v| req[k] = v }
109
+ yield req if block
110
+ end
111
+ rescue Faraday::ClientError, Faraday::ServerError => e # Faraday::Response::RaiseError
112
+ status =
113
+ if e.respond_to?(:response_status)
114
+ e.response_status
115
+ elsif e.response && e.response.respond_to?(:status)
116
+ e.response.status
117
+ elsif e.response && e.response.respond_to?(:fetch)
118
+ e.response[:status]
119
+ end
120
+ http = span&.context&.http
121
+ if http && status
122
+ http.status_code = status.to_s
123
+ span.outcome = Span::Outcome.from_http_status(status)
124
+ end
125
+ raise e
110
126
  end
111
127
 
112
128
  if (http = span&.context&.http)