elastic-apm 1.1.0 → 2.0.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.

Potentially problematic release.


This version of elastic-apm might be problematic. Click here for more details.

Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +7 -1
  5. data/CHANGELOG.md +45 -0
  6. data/Gemfile +17 -12
  7. data/bench/app.rb +1 -2
  8. data/bench/benchmark.rb +1 -1
  9. data/bench/stackprof.rb +1 -1
  10. data/docs/api.asciidoc +115 -76
  11. data/docs/configuration.asciidoc +232 -167
  12. data/docs/context.asciidoc +7 -3
  13. data/docs/custom-instrumentation.asciidoc +17 -28
  14. data/docs/index.asciidoc +13 -7
  15. data/docs/supported-technologies.asciidoc +65 -0
  16. data/elastic-apm.gemspec +3 -2
  17. data/lib/elastic_apm.rb +272 -121
  18. data/lib/elastic_apm/agent.rb +56 -107
  19. data/lib/elastic_apm/config.rb +130 -106
  20. data/lib/elastic_apm/config/duration.rb +25 -0
  21. data/lib/elastic_apm/config/size.rb +28 -0
  22. data/lib/elastic_apm/context_builder.rb +1 -0
  23. data/lib/elastic_apm/deprecations.rb +19 -0
  24. data/lib/elastic_apm/error.rb +5 -2
  25. data/lib/elastic_apm/error/exception.rb +1 -1
  26. data/lib/elastic_apm/error_builder.rb +5 -0
  27. data/lib/elastic_apm/instrumenter.rb +121 -53
  28. data/lib/elastic_apm/internal_error.rb +1 -0
  29. data/lib/elastic_apm/{log.rb → logging.rb} +16 -11
  30. data/lib/elastic_apm/metadata.rb +20 -0
  31. data/lib/elastic_apm/metadata/process_info.rb +26 -0
  32. data/lib/elastic_apm/metadata/service_info.rb +56 -0
  33. data/lib/elastic_apm/metadata/system_info.rb +30 -0
  34. data/lib/elastic_apm/middleware.rb +31 -15
  35. data/lib/elastic_apm/normalizers/action_controller.rb +1 -1
  36. data/lib/elastic_apm/normalizers/action_mailer.rb +1 -1
  37. data/lib/elastic_apm/normalizers/action_view.rb +3 -3
  38. data/lib/elastic_apm/normalizers/active_record.rb +2 -1
  39. data/lib/elastic_apm/railtie.rb +1 -1
  40. data/lib/elastic_apm/span.rb +59 -29
  41. data/lib/elastic_apm/span/context.rb +30 -4
  42. data/lib/elastic_apm/span_helpers.rb +1 -1
  43. data/lib/elastic_apm/spies/delayed_job.rb +7 -7
  44. data/lib/elastic_apm/spies/elasticsearch.rb +4 -4
  45. data/lib/elastic_apm/spies/http.rb +38 -0
  46. data/lib/elastic_apm/spies/mongo.rb +22 -11
  47. data/lib/elastic_apm/spies/net_http.rb +7 -4
  48. data/lib/elastic_apm/spies/rake.rb +5 -6
  49. data/lib/elastic_apm/spies/redis.rb +1 -1
  50. data/lib/elastic_apm/spies/sequel.rb +9 -7
  51. data/lib/elastic_apm/spies/sidekiq.rb +5 -5
  52. data/lib/elastic_apm/spies/tilt.rb +2 -2
  53. data/lib/elastic_apm/sql_summarizer.rb +3 -3
  54. data/lib/elastic_apm/stacktrace_builder.rb +6 -6
  55. data/lib/elastic_apm/subscriber.rb +3 -3
  56. data/lib/elastic_apm/traceparent.rb +62 -0
  57. data/lib/elastic_apm/transaction.rb +62 -93
  58. data/lib/elastic_apm/transport/base.rb +98 -0
  59. data/lib/elastic_apm/transport/connection.rb +175 -0
  60. data/lib/elastic_apm/transport/filters.rb +45 -0
  61. data/lib/elastic_apm/transport/filters/request_body_filter.rb +31 -0
  62. data/lib/elastic_apm/transport/filters/secrets_filter.rb +59 -0
  63. data/lib/elastic_apm/transport/serializers.rb +58 -0
  64. data/lib/elastic_apm/transport/serializers/error_serializer.rb +59 -0
  65. data/lib/elastic_apm/transport/serializers/span_serializer.rb +30 -0
  66. data/lib/elastic_apm/transport/serializers/transaction_serializer.rb +33 -0
  67. data/lib/elastic_apm/transport/worker.rb +73 -0
  68. data/lib/elastic_apm/util.rb +11 -8
  69. data/lib/elastic_apm/version.rb +1 -1
  70. metadata +40 -21
  71. data/.travis.yml +0 -5
  72. data/docs/troubleshooting.asciidoc +0 -28
  73. data/lib/elastic_apm/filters.rb +0 -46
  74. data/lib/elastic_apm/filters/request_body_filter.rb +0 -33
  75. data/lib/elastic_apm/filters/secrets_filter.rb +0 -59
  76. data/lib/elastic_apm/http.rb +0 -139
  77. data/lib/elastic_apm/process_info.rb +0 -24
  78. data/lib/elastic_apm/serializers.rb +0 -28
  79. data/lib/elastic_apm/serializers/errors.rb +0 -61
  80. data/lib/elastic_apm/serializers/transactions.rb +0 -51
  81. data/lib/elastic_apm/service_info.rb +0 -54
  82. data/lib/elastic_apm/system_info.rb +0 -28
  83. data/lib/elastic_apm/util/dig.rb +0 -31
  84. data/lib/elastic_apm/util/inspector.rb +0 -61
  85. data/lib/elastic_apm/worker.rb +0 -106
@@ -27,7 +27,7 @@ module ElasticAPM
27
27
  return __without_apm_#{method}(*args, &block)
28
28
  end
29
29
 
30
- ElasticAPM.span "#{name}", "#{type}" do
30
+ ElasticAPM.with_span "#{name}", "#{type}" do
31
31
  __without_apm_#{method}(*args, &block)
32
32
  end
33
33
  end
@@ -5,9 +5,9 @@ module ElasticAPM
5
5
  module Spies
6
6
  # @api private
7
7
  class DelayedJobSpy
8
- CLASS_SEPARATOR = '.'.freeze
9
- METHOD_SEPARATOR = '#'.freeze
10
- TYPE = 'Delayed::Job'.freeze
8
+ CLASS_SEPARATOR = '.'
9
+ METHOD_SEPARATOR = '#'
10
+ TYPE = 'Delayed::Job'
11
11
 
12
12
  def install
13
13
  ::Delayed::Backend::Base.class_eval do
@@ -22,15 +22,15 @@ module ElasticAPM
22
22
 
23
23
  def self.invoke_job(job, *args, &block)
24
24
  job_name = name_from_payload(job.payload_object)
25
- transaction = ElasticAPM.transaction(job_name, TYPE)
25
+ transaction = ElasticAPM.start_transaction(job_name, TYPE)
26
26
  job.invoke_job_without_apm(*args, &block)
27
- transaction.submit 'success'
27
+ transaction.done 'success'
28
28
  rescue ::Exception => e
29
29
  ElasticAPM.report(e, handled: false)
30
- transaction.submit 'error'
30
+ transaction.done 'error'
31
31
  raise
32
32
  ensure
33
- transaction.release if transaction
33
+ ElasticAPM.end_transaction
34
34
  end
35
35
 
36
36
  def self.name_from_payload(payload_object)
@@ -5,8 +5,8 @@ module ElasticAPM
5
5
  module Spies
6
6
  # @api private
7
7
  class ElasticsearchSpy
8
- NAME_FORMAT = '%s %s'.freeze
9
- TYPE = 'db.elasticsearch'.freeze
8
+ NAME_FORMAT = '%s %s'
9
+ TYPE = 'db.elasticsearch'
10
10
 
11
11
  # rubocop:disable Metrics/MethodLength
12
12
  def install
@@ -16,9 +16,9 @@ module ElasticAPM
16
16
  def perform_request(method, path, *args, &block)
17
17
  name = format(NAME_FORMAT, method, path)
18
18
  statement = args[0].is_a?(String) ? args[0] : args[0].to_json
19
- context = Span::Context.new(statement: statement)
19
+ context = Span::Context.new(db: { statement: statement })
20
20
 
21
- ElasticAPM.span name, TYPE, context: context do
21
+ ElasticAPM.with_span name, TYPE, context: context do
22
22
  perform_request_without_apm(method, path, *args, &block)
23
23
  end
24
24
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ module Spies
6
+ # @api private
7
+ class HTTPSpy
8
+ # rubocop:disable Metrics/MethodLength
9
+ def install
10
+ ::HTTP::Client.class_eval do
11
+ alias perform_without_apm perform
12
+
13
+ def perform(req, options)
14
+ unless (transaction = ElasticAPM.current_transaction)
15
+ return perform_without_apm(req, options)
16
+ end
17
+
18
+ method = req.verb.to_s.upcase
19
+ host = req.uri.host
20
+
21
+ name = "#{method} #{host}"
22
+ type = "ext.http_rb.#{method}"
23
+
24
+ ElasticAPM.with_span name, type do |span|
25
+ req['Elastic-Apm-Traceparent'] =
26
+ transaction.traceparent.to_header(span_id: span.id)
27
+
28
+ perform_without_apm(req, options)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ # rubocop:enable Metrics/MethodLength
34
+ end
35
+
36
+ register 'HTTP', 'http', HTTPSpy.new
37
+ end
38
+ end
@@ -14,7 +14,7 @@ module ElasticAPM
14
14
 
15
15
  # @api private
16
16
  class Subscriber
17
- TYPE = 'db.mongodb.query'.freeze
17
+ TYPE = 'db.mongodb.query'
18
18
 
19
19
  def initialize
20
20
  @events = {}
@@ -37,21 +37,32 @@ module ElasticAPM
37
37
  def push_event(event)
38
38
  return unless ElasticAPM.current_transaction
39
39
 
40
- ctx = Span::Context.new(
41
- instance: event.database_name,
42
- statement: nil,
43
- type: 'mongodb'.freeze,
44
- user: nil
45
- )
46
- span = ElasticAPM.span(event.command_name.to_s, TYPE, context: ctx)
40
+ span =
41
+ ElasticAPM.start_span(
42
+ event.command_name.to_s,
43
+ TYPE,
44
+ context: build_context(event)
45
+ )
46
+
47
47
  @events[event.operation_id] = span
48
48
  end
49
49
 
50
50
  def pop_event(event)
51
- return unless ElasticAPM.current_transaction
52
-
51
+ return unless (curr = ElasticAPM.current_span)
53
52
  span = @events.delete(event.operation_id)
54
- span && span.done
53
+
54
+ curr == span && ElasticAPM.end_span
55
+ end
56
+
57
+ def build_context(event)
58
+ Span::Context.new(
59
+ db: {
60
+ instance: event.database_name,
61
+ statement: nil,
62
+ type: 'mongodb',
63
+ user: nil
64
+ }
65
+ )
55
66
  end
56
67
  end
57
68
  end
@@ -5,13 +5,13 @@ module ElasticAPM
5
5
  module Spies
6
6
  # @api private
7
7
  class NetHTTPSpy
8
- # rubocop:disable Metrics/MethodLength
8
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
9
9
  def install
10
10
  Net::HTTP.class_eval do
11
11
  alias request_without_apm request
12
12
 
13
13
  def request(req, body = nil, &block)
14
- unless ElasticAPM.current_transaction
14
+ unless (transaction = ElasticAPM.current_transaction)
15
15
  return request_without_apm(req, body, &block)
16
16
  end
17
17
 
@@ -23,13 +23,16 @@ module ElasticAPM
23
23
  name = "#{method} #{host}"
24
24
  type = "ext.net_http.#{method}"
25
25
 
26
- ElasticAPM.span name, type do
26
+ ElasticAPM.with_span name, type do |span|
27
+ req['Elastic-Apm-Traceparent'] =
28
+ transaction.traceparent.to_header(span_id: span.id)
29
+
27
30
  request_without_apm(req, body, &block)
28
31
  end
29
32
  end
30
33
  end
31
34
  end
32
- # rubocop:enable Metrics/MethodLength
35
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
33
36
  end
34
37
 
35
38
  register 'Net::HTTP', 'net/http', NetHTTPSpy.new
@@ -6,7 +6,6 @@ module ElasticAPM
6
6
  # @api private
7
7
  class RakeSpy
8
8
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
9
- # rubocop:disable Metrics/CyclomaticComplexity
10
9
  def install
11
10
  ::Rake::Task.class_eval do
12
11
  alias execute_without_apm execute
@@ -18,19 +17,20 @@ module ElasticAPM
18
17
  return execute_without_apm(*args)
19
18
  end
20
19
 
21
- transaction = ElasticAPM.transaction("Rake::Task[#{name}]", 'Rake')
20
+ transaction =
21
+ ElasticAPM.start_transaction("Rake::Task[#{name}]", 'Rake')
22
22
 
23
23
  begin
24
24
  result = execute_without_apm(*args)
25
25
 
26
- transaction.submit('success') if transaction
26
+ transaction.result = 'success' if transaction
27
27
  rescue StandardError => e
28
- transaction.submit(:error) if transaction
28
+ transaction.result = 'error' if transaction
29
29
  ElasticAPM.report(e)
30
30
 
31
31
  raise
32
32
  ensure
33
- transaction.release if transaction
33
+ ElasticAPM.end_transaction
34
34
  ElasticAPM.stop
35
35
  end
36
36
 
@@ -38,7 +38,6 @@ module ElasticAPM
38
38
  end
39
39
  end
40
40
  end
41
- # rubocop:enable Metrics/CyclomaticComplexity
42
41
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
43
42
  end
44
43
  register 'Rake::Task', 'rake', RakeSpy.new
@@ -14,7 +14,7 @@ module ElasticAPM
14
14
 
15
15
  return call_without_apm(command, &block) if command[0] == :auth
16
16
 
17
- ElasticAPM.span(name.to_s, 'db.redis') do
17
+ ElasticAPM.with_span(name.to_s, 'db.redis') do
18
18
  call_without_apm(command, &block)
19
19
  end
20
20
  end
@@ -7,12 +7,18 @@ module ElasticAPM
7
7
  module Spies
8
8
  # @api private
9
9
  class SequelSpy
10
- TYPE = 'db.sequel.sql'.freeze
10
+ TYPE = 'db.sequel.sql'
11
11
 
12
12
  def self.summarizer
13
13
  @summarizer ||= SqlSummarizer.new
14
14
  end
15
15
 
16
+ def self.build_context(sql, opts)
17
+ Span::Context.new(
18
+ db: { statement: sql, type: 'sql', user: opts[:user] }
19
+ )
20
+ end
21
+
16
22
  # rubocop:disable Metrics/MethodLength
17
23
  def install
18
24
  require 'sequel/database/logging'
@@ -27,13 +33,9 @@ module ElasticAPM
27
33
 
28
34
  summarizer = ElasticAPM::Spies::SequelSpy.summarizer
29
35
  name = summarizer.summarize sql
30
- context = Span::Context.new(
31
- statement: sql,
32
- type: 'sql',
33
- user: opts[:user]
34
- )
36
+ context = ElasticAPM::Spies::SequelSpy.build_context(sql, opts)
35
37
 
36
- ElasticAPM.span(name, TYPE, context: context, &block)
38
+ ElasticAPM.with_span(name, TYPE, context: context, &block)
37
39
  end
38
40
  end
39
41
  end
@@ -6,25 +6,25 @@ module ElasticAPM
6
6
  # @api private
7
7
  class SidekiqSpy
8
8
  ACTIVE_JOB_WRAPPER =
9
- 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper'.freeze
9
+ 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper'
10
10
 
11
11
  # @api private
12
12
  class Middleware
13
13
  # rubocop:disable Metrics/MethodLength
14
14
  def call(_worker, job, queue)
15
15
  name = SidekiqSpy.name_for(job)
16
- transaction = ElasticAPM.transaction(name, 'Sidekiq')
16
+ transaction = ElasticAPM.start_transaction(name, 'Sidekiq')
17
17
  ElasticAPM.set_tag(:queue, queue)
18
18
 
19
19
  yield
20
20
 
21
- transaction.submit('success') if transaction
21
+ transaction.done :success if transaction
22
22
  rescue ::Exception => e
23
23
  ElasticAPM.report(e, handled: false)
24
- transaction.submit(:error) if transaction
24
+ transaction.done :error if transaction
25
25
  raise
26
26
  ensure
27
- transaction.release if transaction
27
+ ElasticAPM.end_transaction
28
28
  end
29
29
  # rubocop:enable Metrics/MethodLength
30
30
  end
@@ -5,7 +5,7 @@ module ElasticAPM
5
5
  module Spies
6
6
  # @api private
7
7
  class TiltSpy
8
- TYPE = 'template.tilt'.freeze
8
+ TYPE = 'template.tilt'
9
9
 
10
10
  def install
11
11
  ::Tilt::Template.class_eval do
@@ -14,7 +14,7 @@ module ElasticAPM
14
14
  def render(*args, &block)
15
15
  name = options[:__elastic_apm_template_name] || 'Unknown template'
16
16
 
17
- ElasticAPM.span name, TYPE do
17
+ ElasticAPM.with_span name, TYPE do
18
18
  render_without_apm(*args, &block)
19
19
  end
20
20
  end
@@ -5,8 +5,8 @@ require 'elastic_apm/util/lru_cache'
5
5
  module ElasticAPM
6
6
  # @api private
7
7
  class SqlSummarizer
8
- DEFAULT = 'SQL'.freeze
9
- TABLE_REGEX = %{["'`]?([A-Za-z0-9]+)}.freeze
8
+ DEFAULT = 'SQL'
9
+ TABLE_REGEX = %{["'`]?([A-Za-z0-9]+)}
10
10
 
11
11
  REGEXES = {
12
12
  /^BEGIN/i => 'BEGIN',
@@ -17,7 +17,7 @@ module ElasticAPM
17
17
  /^DELETE FROM #{TABLE_REGEX}/i => 'DELETE FROM '
18
18
  }.freeze
19
19
 
20
- FORMAT = '%s%s'.freeze
20
+ FORMAT = '%s%s'
21
21
 
22
22
  def self.cache
23
23
  @cache ||= Util::LruCache.new
@@ -6,14 +6,14 @@ require 'elastic_apm/util/lru_cache'
6
6
  module ElasticAPM
7
7
  # @api private
8
8
  class StacktraceBuilder
9
- JAVA_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/
10
- RUBY_FORMAT = /^(.+?):(\d+)(?::in `(.+?)')?$/
9
+ JAVA_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/.freeze
10
+ RUBY_FORMAT = /^(.+?):(\d+)(?::in `(.+?)')?$/.freeze
11
11
 
12
- RUBY_VERS_REGEX = %r{ruby(/gems)?[-/](\d+\.)+\d}
13
- JRUBY_ORG_REGEX = %r{org/jruby}
12
+ RUBY_VERS_REGEX = %r{ruby(/gems)?[-/](\d+\.)+\d}.freeze
13
+ JRUBY_ORG_REGEX = %r{org/jruby}.freeze
14
14
 
15
- def initialize(agent)
16
- @config = agent.config
15
+ def initialize(config)
16
+ @config = config
17
17
  @cache = Util::LruCache.new(2048, &method(:build_frame))
18
18
  end
19
19
 
@@ -6,7 +6,7 @@ require 'elastic_apm/normalizers'
6
6
  module ElasticAPM
7
7
  # @api private
8
8
  class Subscriber
9
- include Log
9
+ include Logging
10
10
 
11
11
  def initialize(agent)
12
12
  @agent = agent
@@ -40,7 +40,7 @@ module ElasticAPM
40
40
  nil
41
41
  else
42
42
  name, type, context = normalized
43
- @agent.span(name, type, context: context)
43
+ @agent.start_span(name, type, context: context)
44
44
  end
45
45
 
46
46
  transaction.notifications << Notification.new(id, span)
@@ -54,7 +54,7 @@ module ElasticAPM
54
54
  next unless notification.id == id
55
55
 
56
56
  if (span = notification.span)
57
- span.done
57
+ @agent.end_span if span == @agent.current_span
58
58
  end
59
59
  return
60
60
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ class Traceparent
6
+ class InvalidTraceparentHeader < StandardError; end
7
+
8
+ VERSION = '00'
9
+ HEX_REGEX = /[^[:xdigit:]]/.freeze
10
+
11
+ def initialize
12
+ @version = VERSION
13
+ end
14
+
15
+ def self.from_transaction(transaction)
16
+ new.tap do |t|
17
+ t.trace_id = SecureRandom.hex(16)
18
+ t.recorded = transaction.sampled?
19
+ end
20
+ end
21
+
22
+ # rubocop:disable Metrics/AbcSize
23
+ def self.parse(header)
24
+ raise InvalidTraceparentHeader unless header.length == 55
25
+ raise InvalidTraceparentHeader unless header[0..1] == VERSION
26
+
27
+ new.tap do |t|
28
+ t.version, t.trace_id, t.span_id, t.flags =
29
+ header.split('-').tap do |values|
30
+ values[-1] = Util.hex_to_bits(values[-1])
31
+ end
32
+
33
+ raise InvalidTraceparentHeader if HEX_REGEX =~ t.trace_id
34
+ raise InvalidTraceparentHeader if HEX_REGEX =~ t.span_id
35
+ end
36
+ end
37
+ # rubocop:enable Metrics/AbcSize
38
+
39
+ attr_accessor :header, :version, :trace_id, :span_id, :recorded
40
+
41
+ alias :recorded? :recorded
42
+
43
+ def flags=(flags)
44
+ @flags = flags
45
+
46
+ self.recorded = flags[7] == '1'
47
+ end
48
+
49
+ def flags
50
+ format('0000000%d', recorded? ? 1 : 0)
51
+ end
52
+
53
+ def hex_flags
54
+ format('%02x', flags.to_i(2))
55
+ end
56
+
57
+ def to_header(span_id: nil)
58
+ span_id ||= self.span_id
59
+ format('%s-%s-%s-%s', version, trace_id, span_id, hex_flags)
60
+ end
61
+ end
62
+ end