elastic-apm 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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