elastic-apm 3.1.0 → 3.2.0

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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/.jenkins_exclude.yml +47 -0
  3. data/.ci/.jenkins_framework.yml +4 -0
  4. data/.ci/.jenkins_master_framework.yml +1 -0
  5. data/.ci/.jenkins_ruby.yml +1 -0
  6. data/.ci/downstreamTests.groovy +1 -1
  7. data/.gitignore +2 -1
  8. data/.rspec +1 -0
  9. data/CHANGELOG.asciidoc +24 -0
  10. data/Dockerfile +43 -0
  11. data/Gemfile +34 -15
  12. data/README.md +30 -1
  13. data/bin/dev +54 -0
  14. data/bin/run-tests +27 -0
  15. data/docker-compose.yml +32 -0
  16. data/docs/api.asciidoc +13 -2
  17. data/docs/configuration.asciidoc +30 -0
  18. data/docs/getting-started-rack.asciidoc +24 -0
  19. data/docs/release-notes.asciidoc +1 -1
  20. data/lib/elastic_apm.rb +12 -1
  21. data/lib/elastic_apm/agent.rb +15 -3
  22. data/lib/elastic_apm/central_config.rb +39 -19
  23. data/lib/elastic_apm/child_durations.rb +42 -0
  24. data/lib/elastic_apm/config.rb +27 -11
  25. data/lib/elastic_apm/context/request/socket.rb +1 -1
  26. data/lib/elastic_apm/context_builder.rb +1 -1
  27. data/lib/elastic_apm/error.rb +10 -0
  28. data/lib/elastic_apm/error/exception.rb +7 -0
  29. data/lib/elastic_apm/grape.rb +48 -0
  30. data/lib/elastic_apm/instrumenter.rb +77 -4
  31. data/lib/elastic_apm/logging.rb +0 -2
  32. data/lib/elastic_apm/metrics.rb +39 -26
  33. data/lib/elastic_apm/metrics/breakdown_set.rb +14 -0
  34. data/lib/elastic_apm/metrics/{cpu_mem.rb → cpu_mem_set.rb} +62 -54
  35. data/lib/elastic_apm/metrics/metric.rb +117 -0
  36. data/lib/elastic_apm/metrics/set.rb +106 -0
  37. data/lib/elastic_apm/metrics/span_scoped_set.rb +39 -0
  38. data/lib/elastic_apm/metrics/transaction_set.rb +11 -0
  39. data/lib/elastic_apm/metrics/vm_set.rb +44 -0
  40. data/lib/elastic_apm/metricset.rb +31 -4
  41. data/lib/elastic_apm/normalizers.rb +6 -0
  42. data/lib/elastic_apm/normalizers/grape.rb +5 -0
  43. data/lib/elastic_apm/normalizers/grape/endpoint_run.rb +47 -0
  44. data/lib/elastic_apm/normalizers/rails/active_record.rb +16 -5
  45. data/lib/elastic_apm/opentracing.rb +4 -4
  46. data/lib/elastic_apm/rails.rb +12 -2
  47. data/lib/elastic_apm/railtie.rb +1 -5
  48. data/lib/elastic_apm/sinatra.rb +1 -1
  49. data/lib/elastic_apm/span.rb +15 -10
  50. data/lib/elastic_apm/spies.rb +0 -1
  51. data/lib/elastic_apm/sql_summarizer.rb +8 -6
  52. data/lib/elastic_apm/subscriber.rb +4 -1
  53. data/lib/elastic_apm/transaction.rb +6 -6
  54. data/lib/elastic_apm/transport/base.rb +7 -0
  55. data/lib/elastic_apm/transport/connection.rb +11 -69
  56. data/lib/elastic_apm/transport/connection/http.rb +43 -35
  57. data/lib/elastic_apm/transport/connection/proxy_pipe.rb +0 -3
  58. data/lib/elastic_apm/transport/headers.rb +62 -0
  59. data/lib/elastic_apm/transport/serializers.rb +0 -2
  60. data/lib/elastic_apm/transport/serializers/metricset_serializer.rb +19 -6
  61. data/lib/elastic_apm/transport/serializers/span_serializer.rb +3 -3
  62. data/lib/elastic_apm/transport/user_agent.rb +31 -0
  63. data/lib/elastic_apm/transport/worker.rb +1 -2
  64. data/lib/elastic_apm/version.rb +1 -1
  65. metadata +20 -6
  66. data/lib/elastic_apm/metrics/vm.rb +0 -60
  67. data/lib/elastic_apm/util/prefixed_logger.rb +0 -18
@@ -193,9 +193,9 @@ Returns the built context.
193
193
 
194
194
  [float]
195
195
  [[rails-start]]
196
- === Manually hooking into Rails
196
+ === Rails
197
197
 
198
- Start the agent and hook into Rails explicitly. This is useful if you skip requiring
198
+ Start the agent and hook into Rails manually. This is useful if you skip requiring
199
199
  the gem and using the `Railtie`.
200
200
 
201
201
  [source,ruby]
@@ -214,6 +214,17 @@ Start the agent and hook into Sinatra.
214
214
  ElasticAPM::Sinatra.start(MySinatraApp, server_url: 'http://localhost:8200')
215
215
  ----
216
216
 
217
+ [float]
218
+ [[grape-start]]
219
+ === Grape
220
+
221
+ Start the agent and hook into Grape.
222
+
223
+ [source,ruby]
224
+ ----
225
+ ElasticAPM::Grape.start(MyGrapeApp, server_url: 'http://localhost:8200')
226
+ ----
227
+
217
228
  [float]
218
229
  === Errors
219
230
 
@@ -66,6 +66,23 @@ ElasticAPM::Sinatra.start(
66
66
 
67
67
  See <<getting-started-rack>>.
68
68
 
69
+ [float]
70
+ === Grape and Rack
71
+
72
+ When using APM with Grape and Rack (without Rails), you can configure it when starting
73
+ the agent:
74
+
75
+ [source,ruby]
76
+ ----
77
+ # config.ru or similar
78
+ ElasticAPM::Grape.start(
79
+ MyApp,
80
+ service_name: 'SomeOtherName'
81
+ )
82
+ ----
83
+
84
+ See <<getting-started-rack>>.
85
+
69
86
  [float]
70
87
  === Options
71
88
 
@@ -184,6 +201,19 @@ APM Server has its own limit of 30 seconds before it will close requests.
184
201
 
185
202
  It has to be provided in *<<config-format-duration, duration format>>*.
186
203
 
204
+ [float]
205
+ [[config-breakdown-metrics]]
206
+ ==== `breakdown-metrics`
207
+ |============
208
+ | Environment | `Config` key | Default
209
+ | `ELASTIC_APM_BREAKDOWN_METRICS` | `breakdown_metrics` | `true`
210
+ |============
211
+
212
+ Enable/disable the tracking and collection of breakdown metrics.
213
+ By setting this to `False`, tracking this metric is completely disabled, which can reduce the overhead of the agent.
214
+
215
+ NOTE: This feature requires APM Server and Kibana >= 7.3.
216
+
187
217
  [float]
188
218
  [[config-capture-body]]
189
219
  ==== `capture_body`
@@ -72,3 +72,27 @@ run MySinatraApp
72
72
  at_exit { ElasticAPM.stop }
73
73
  ----
74
74
 
75
+ [float]
76
+ [[getting-started-grape]]
77
+ ==== Grape example
78
+
79
+ [source,ruby]
80
+ ----
81
+ # Example config.ru
82
+
83
+ require 'grape'
84
+
85
+ module Twitter
86
+ class API < Grape::API
87
+ use ElasticAPM::Middleware
88
+
89
+ # ...
90
+ end
91
+ end
92
+
93
+ # Start the agent and hook in your app
94
+ ElasticAPM::Grape.start(Twitter::API, config)
95
+
96
+ run Twitter::API
97
+
98
+ ----
@@ -1,4 +1,4 @@
1
- :pull: https://github.com/elastic/apm-server/pull/
1
+ :pull: https://github.com/elastic/apm-agent-ruby/pull/
2
2
 
3
3
  [[release-notes]]
4
4
  == Release notes
@@ -1,5 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'erb'
4
+ require 'http'
5
+ require 'json'
6
+ require 'yaml'
7
+ require 'zlib'
8
+ require 'logger'
9
+ require 'concurrent'
10
+ require 'forwardable'
11
+ require 'securerandom'
12
+
3
13
  require 'elastic_apm/version'
4
14
  require 'elastic_apm/internal_error'
5
15
  require 'elastic_apm/logging'
@@ -13,8 +23,9 @@ require 'elastic_apm/util'
13
23
 
14
24
  require 'elastic_apm/middleware'
15
25
 
16
- require 'elastic_apm/railtie' if defined?(::Rails::Railtie)
26
+ require 'elastic_apm/rails' if defined?(::Rails::Railtie)
17
27
  require 'elastic_apm/sinatra' if defined?(::Sinatra)
28
+ require 'elastic_apm/grape' if defined?(::Grape)
18
29
 
19
30
  # ElasticAPM
20
31
  module ElasticAPM # rubocop:disable Metrics/ModuleLength
@@ -62,6 +62,7 @@ module ElasticAPM
62
62
  !!@instance
63
63
  end
64
64
 
65
+ # rubocop:disable Metrics/MethodLength
65
66
  def initialize(config)
66
67
  @stacktrace_builder = StacktraceBuilder.new(config)
67
68
  @context_builder = ContextBuilder.new(config)
@@ -69,12 +70,14 @@ module ElasticAPM
69
70
 
70
71
  @central_config = CentralConfig.new(config)
71
72
  @transport = Transport::Base.new(config)
73
+ @metrics = Metrics.new(config) { |event| enqueue event }
72
74
  @instrumenter = Instrumenter.new(
73
75
  config,
76
+ metrics: metrics,
74
77
  stacktrace_builder: stacktrace_builder
75
78
  ) { |event| enqueue event }
76
- @metrics = Metrics.new(config) { |event| enqueue event }
77
79
  end
80
+ # rubocop:enable Metrics/MethodLength
78
81
 
79
82
  attr_reader(
80
83
  :central_config,
@@ -91,8 +94,11 @@ module ElasticAPM
91
94
 
92
95
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
93
96
  def start
94
- unless config.disable_start_message
95
- info '[%s] Starting agent, reporting to %s', VERSION, config.server_url
97
+ unless config.disable_start_message?
98
+ config.logger.info format(
99
+ '[%s] Starting agent, reporting to %s',
100
+ VERSION, config.server_url
101
+ )
96
102
  end
97
103
 
98
104
  central_config.start
@@ -231,6 +237,12 @@ module ElasticAPM
231
237
  def add_filter(key, callback)
232
238
  transport.add_filter(key, callback)
233
239
  end
240
+
241
+ # misc
242
+
243
+ def inspect
244
+ super.split.first + '>'
245
+ end
234
246
  end
235
247
  # rubocop:enable Metrics/ClassLength
236
248
  end
@@ -4,7 +4,7 @@ require 'elastic_apm/central_config/cache_control'
4
4
 
5
5
  module ElasticAPM
6
6
  # @api private
7
- class CentralConfig
7
+ class CentralConfig # rubocop:disable Metrics/ClassLength
8
8
  include Logging
9
9
 
10
10
  # @api private
@@ -23,10 +23,8 @@ module ElasticAPM
23
23
  def initialize(config)
24
24
  @config = config
25
25
  @modified_options = {}
26
- @service_info = {
27
- 'service.name': config.service_name,
28
- 'service.environment': config.environment
29
- }.to_json
26
+ @http = Transport::Connection::Http.new(config)
27
+ @etag = 1
30
28
  end
31
29
 
32
30
  attr_reader :config
@@ -35,9 +33,17 @@ module ElasticAPM
35
33
  def start
36
34
  return unless config.central_config?
37
35
 
36
+ debug 'Starting CentralConfig'
37
+
38
38
  fetch_and_apply_config
39
39
  end
40
40
 
41
+ def stop
42
+ debug 'Stopping CentralConfig'
43
+
44
+ @scheduled_task&.cancel
45
+ end
46
+
41
47
  def fetch_and_apply_config
42
48
  @promise =
43
49
  Concurrent::Promise
@@ -46,10 +52,6 @@ module ElasticAPM
46
52
  .rescue(&method(:handle_error))
47
53
  end
48
54
 
49
- def stop
50
- @scheduled_task&.cancel
51
- end
52
-
53
55
  # rubocop:disable Metrics/MethodLength
54
56
  def fetch_config
55
57
  resp = perform_request
@@ -90,15 +92,25 @@ module ElasticAPM
90
92
  @config = config.dup.tap { |new_config| new_config.assign(new_options) }
91
93
  end
92
94
 
93
- # rubocop:disable Metrics/MethodLength
95
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
96
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
94
97
  def handle_success(resp)
98
+ if (etag = resp.headers['Etag'])
99
+ @etag = etag
100
+ end
101
+
95
102
  if resp.status == 304
96
103
  info 'Received 304 Not Modified'
97
104
  else
98
- update = JSON.parse(resp.body.to_s)
99
- assign(update)
105
+ if resp.body && !resp.body.empty?
106
+ update = JSON.parse(resp.body.to_s)
107
+ assign(update)
108
+ end
100
109
 
101
- info 'Updated config from Kibana'
110
+ if @modified_options.any?
111
+ info 'Updated config from Kibana'
112
+ debug 'Modified: %s', @modified_options.inspect
113
+ end
102
114
  end
103
115
 
104
116
  schedule_next_fetch(resp)
@@ -108,7 +120,8 @@ module ElasticAPM
108
120
  error 'Failed to apply remote config, %s', e.inspect
109
121
  debug { e.backtrace.join('\n') }
110
122
  end
111
- # rubocop:enable Metrics/MethodLength
123
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
124
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
112
125
 
113
126
  def handle_error(error)
114
127
  debug(
@@ -122,11 +135,18 @@ module ElasticAPM
122
135
  end
123
136
 
124
137
  def perform_request
125
- Http.post(
126
- config.server_url + '/config/v1/agents',
127
- body: @service_info,
128
- headers: { etag: 1, content_type: 'application/json' }
129
- )
138
+ @http.get(server_url, headers: headers)
139
+ end
140
+
141
+ def server_url
142
+ @server_url ||=
143
+ config.server_url +
144
+ '/config/v1/agents' \
145
+ "?service.name=#{config.service_name}"
146
+ end
147
+
148
+ def headers
149
+ { 'Etag': @etag }
130
150
  end
131
151
 
132
152
  def schedule_next_fetch(resp)
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ module ChildDurations
6
+ # @api private
7
+ module Methods
8
+ def child_durations
9
+ @child_durations ||= Durations.new
10
+ end
11
+
12
+ def child_started
13
+ child_durations.start
14
+ end
15
+
16
+ def child_stopped
17
+ child_durations.stop
18
+ end
19
+ end
20
+
21
+ # @api private
22
+ class Durations
23
+ def initialize
24
+ @nesting_level = 0
25
+ @start = nil
26
+ @duration = 0
27
+ end
28
+
29
+ attr_reader :duration
30
+
31
+ def start
32
+ @nesting_level += 1
33
+ @start = Util.micros if @nesting_level == 1
34
+ end
35
+
36
+ def stop
37
+ @nesting_level -= 1
38
+ @duration = (Util.micros - @start) if @nesting_level == 0
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,11 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'logger'
4
- require 'yaml'
5
- require 'erb'
6
-
7
- require 'elastic_apm/util/prefixed_logger'
8
-
9
3
  require 'elastic_apm/config/options'
10
4
  require 'elastic_apm/config/duration'
11
5
  require 'elastic_apm/config/bytes'
@@ -28,6 +22,7 @@ module ElasticAPM
28
22
  option :api_buffer_size, type: :int, default: 256
29
23
  option :api_request_size, type: :bytes, default: '750kb', converter: Bytes.new
30
24
  option :api_request_time, type: :float, default: '10s', converter: Duration.new
25
+ option :breakdown_metrics, type: :bool, default: true
31
26
  option :capture_body, type: :string, default: 'off'
32
27
  option :capture_headers, type: :bool, default: true
33
28
  option :capture_env, type: :bool, default: true
@@ -79,8 +74,6 @@ module ElasticAPM
79
74
  def initialize(options = {})
80
75
  @options = load_schema
81
76
 
82
- custom_logger = options.delete(:logger)
83
-
84
77
  assign(options)
85
78
 
86
79
  # Pick out config_file specifically as we need it now to load it,
@@ -95,10 +88,10 @@ module ElasticAPM
95
88
 
96
89
  yield self if block_given?
97
90
 
98
- @logger = custom_logger || build_logger
91
+ self.logger ||= build_logger
99
92
 
100
- @__view_paths = []
101
- @__root_path = Dir.pwd
93
+ @__view_paths ||= []
94
+ @__root_path ||= Dir.pwd
102
95
  end
103
96
  # rubocop:enable Metrics/MethodLength
104
97
 
@@ -173,6 +166,29 @@ module ElasticAPM
173
166
  @span_frames_min_duration_us ||= span_frames_min_duration * 1_000_000
174
167
  end
175
168
 
169
+ # rubocop:disable Metrics/MethodLength
170
+ def ssl_context
171
+ return unless use_ssl?
172
+
173
+ @ssl_context ||=
174
+ OpenSSL::SSL::SSLContext.new.tap do |context|
175
+ if server_ca_cert
176
+ context.ca_file = server_ca_cert
177
+ else
178
+ context.cert_store =
179
+ OpenSSL::X509::Store.new.tap(&:set_default_paths)
180
+ end
181
+
182
+ context.verify_mode =
183
+ if verify_server_cert
184
+ OpenSSL::SSL::VERIFY_PEER
185
+ else
186
+ OpenSSL::SSL::VERIFY_NONE
187
+ end
188
+ end
189
+ end
190
+ # rubocop:enable Metrics/MethodLength
191
+
176
192
  def inspect
177
193
  super.split.first + '>'
178
194
  end
@@ -8,7 +8,7 @@ module ElasticAPM
8
8
  # @api private
9
9
  class Socket
10
10
  def initialize(req)
11
- @remote_addr = req.ip
11
+ @remote_addr = req.env['REMOTE_ADDR']
12
12
  @encrypted = req.scheme == 'https'
13
13
  end
14
14
 
@@ -38,7 +38,7 @@ module ElasticAPM
38
38
  request.headers = headers if config.capture_headers?
39
39
  request.env = env if config.capture_env?
40
40
 
41
- request.cookies = req.cookies
41
+ request.cookies = req.cookies.dup
42
42
 
43
43
  context
44
44
  end
@@ -18,5 +18,15 @@ module ElasticAPM
18
18
  attr_accessor :id, :culprit, :exception, :log, :transaction_id,
19
19
  :transaction, :context, :parent_id, :trace_id
20
20
  attr_reader :timestamp
21
+
22
+ def inspect
23
+ "<ElasticAPM::Error id:#{id}" \
24
+ " culprit:#{culprit}" \
25
+ " timestamp:#{timestamp}" \
26
+ " transaction_id:#{transaction_id}" \
27
+ " trace_id:#{trace_id}" \
28
+ " exception:#{exception.inspect}" \
29
+ ">"
30
+ end
21
31
  end
22
32
  end