elastic-apm 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

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