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
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elastic_apm/metadata/service_info'
4
+ require 'elastic_apm/metadata/system_info'
5
+ require 'elastic_apm/metadata/process_info'
6
+
7
+ module ElasticAPM
8
+ # @api private
9
+ module Metadata
10
+ def self.build(config)
11
+ {
12
+ metadata: {
13
+ service: Metadata::ServiceInfo.build(config),
14
+ process: Metadata::ProcessInfo.build(config),
15
+ system: Metadata::SystemInfo.build(config)
16
+ }
17
+ }.to_json
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ module Metadata
5
+ # @api private
6
+ class ProcessInfo
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ def build
12
+ pid = $PID || Process.pid
13
+ return unless pid
14
+ {
15
+ argv: ARGV,
16
+ pid: pid,
17
+ title: $PROGRAM_NAME
18
+ }
19
+ end
20
+
21
+ def self.build(config)
22
+ new(config).build
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ module Metadata
5
+ # @api private
6
+ class ServiceInfo
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ # rubocop:disable Metrics/MethodLength
12
+ def build
13
+ base = {
14
+ name: @config.service_name,
15
+ environment: @config.environment,
16
+ agent: {
17
+ name: 'ruby',
18
+ version: VERSION
19
+ },
20
+ framework: nil,
21
+ language: {
22
+ name: 'ruby',
23
+ version: RUBY_VERSION
24
+ },
25
+ runtime: runtime,
26
+ version: @config.service_version || Util.git_sha
27
+ }
28
+
29
+ if @config.framework_name
30
+ base[:framework] = {
31
+ name: @config.framework_name,
32
+ version: @config.framework_version
33
+ }
34
+ end
35
+
36
+ base
37
+ end
38
+ # rubocop:enable Metrics/MethodLength
39
+
40
+ def self.build(config)
41
+ new(config).build
42
+ end
43
+
44
+ private
45
+
46
+ def runtime
47
+ case RUBY_ENGINE
48
+ when 'ruby'
49
+ { name: RUBY_ENGINE, version: RUBY_VERSION || RUBY_ENGINE_VERSION }
50
+ when 'jruby'
51
+ { name: RUBY_ENGINE, version: JRUBY_VERSION || RUBY_ENGINE_VERSION }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ module Metadata
5
+ # @api private
6
+ class SystemInfo
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ def build
12
+ {
13
+ hostname: @config.hostname || `hostname`.chomp,
14
+ architecture: platform.cpu,
15
+ platform: platform.os
16
+ }
17
+ end
18
+
19
+ def self.build(config)
20
+ new(config).build
21
+ end
22
+
23
+ private
24
+
25
+ def platform
26
+ @platform ||= Gem::Platform.local
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,40 +1,47 @@
1
+ #
1
2
  # frozen_string_literal: true
2
3
 
4
+ require 'elastic_apm/traceparent'
5
+
3
6
  module ElasticAPM
4
7
  # @api private
5
8
  class Middleware
9
+ include Logging
10
+
6
11
  def initialize(app)
7
12
  @app = app
8
13
  end
9
14
 
10
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
15
+ # rubocop:disable Metrics/MethodLength
11
16
  def call(env)
12
17
  begin
13
18
  if running? && !path_ignored?(env)
14
- transaction = build_transaction(env)
19
+ transaction = start_transaction(env)
15
20
  end
16
21
 
17
22
  resp = @app.call env
18
-
19
- status, headers, body = resp
20
- submit_transaction(transaction, status, headers, body) if transaction
21
23
  rescue InternalError
22
24
  raise # Don't report ElasticAPM errors
23
25
  rescue ::Exception => e
24
26
  ElasticAPM.report(e, handled: false)
25
- transaction.submit('HTTP 5xx', status: 500) if transaction
26
27
  raise
27
28
  ensure
28
- transaction.release if transaction
29
+ if resp && transaction
30
+ status, headers, _body = resp
31
+ transaction.add_response(status, headers: headers)
32
+ end
33
+
34
+ ElasticAPM.end_transaction http_result(status)
29
35
  end
30
36
 
31
37
  resp
32
38
  end
33
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
39
+ # rubocop:enable Metrics/MethodLength
40
+
41
+ private
34
42
 
35
- def submit_transaction(transaction, status, headers, _body)
36
- result = "HTTP #{status.to_s[0]}xx"
37
- transaction.submit(result, status: status, headers: headers)
43
+ def http_result(status)
44
+ status && "HTTP #{status.to_s[0]}xx"
38
45
  end
39
46
 
40
47
  def path_ignored?(env)
@@ -43,9 +50,18 @@ module ElasticAPM
43
50
  end
44
51
  end
45
52
 
46
- def build_transaction(env)
47
- ElasticAPM.transaction 'Rack', 'request',
48
- context: ElasticAPM.build_context(env)
53
+ def start_transaction(env)
54
+ ElasticAPM.start_transaction 'Rack', 'request',
55
+ context: ElasticAPM.build_context(env),
56
+ traceparent: traceparent(env)
57
+ end
58
+
59
+ def traceparent(env)
60
+ return unless (header = env['HTTP_ELASTIC_APM_TRACEPARENT'])
61
+ Traceparent.parse(header)
62
+ rescue Traceparent::InvalidTraceparentHeader
63
+ warn "Couldn't parse invalid traceparent header: #{header.inspect}"
64
+ nil
49
65
  end
50
66
 
51
67
  def running?
@@ -53,7 +69,7 @@ module ElasticAPM
53
69
  end
54
70
 
55
71
  def config
56
- ElasticAPM.agent.config
72
+ @config ||= ElasticAPM.agent.config
57
73
  end
58
74
  end
59
75
  end
@@ -6,7 +6,7 @@ module ElasticAPM
6
6
  # @api private
7
7
  class ProcessActionNormalizer < Normalizer
8
8
  register 'process_action.action_controller'
9
- TYPE = 'app.controller.action'.freeze
9
+ TYPE = 'app.controller.action'
10
10
 
11
11
  def normalize(transaction, _name, payload)
12
12
  transaction.name = endpoint(payload)
@@ -6,7 +6,7 @@ module ElasticAPM
6
6
  # @api private
7
7
  class ProcessActionNormalizer < Normalizer
8
8
  register 'process.action_mailer'
9
- TYPE = 'app.mailer.action'.freeze
9
+ TYPE = 'app.mailer.action'
10
10
 
11
11
  def normalize(_transaction, _name, payload)
12
12
  [endpoint(payload), TYPE, nil]
@@ -41,7 +41,7 @@ module ElasticAPM
41
41
  # @api private
42
42
  class RenderTemplateNormalizer < RenderNormalizer
43
43
  register 'render_template.action_view'
44
- TYPE = 'template.view'.freeze
44
+ TYPE = 'template.view'
45
45
 
46
46
  def normalize(_transaction, _name, payload)
47
47
  normalize_render(payload, TYPE)
@@ -51,7 +51,7 @@ module ElasticAPM
51
51
  # @api private
52
52
  class RenderPartialNormalizer < RenderNormalizer
53
53
  register 'render_partial.action_view'
54
- TYPE = 'template.view.partial'.freeze
54
+ TYPE = 'template.view.partial'
55
55
 
56
56
  def normalize(_transaction, _name, payload)
57
57
  normalize_render(payload, TYPE)
@@ -61,7 +61,7 @@ module ElasticAPM
61
61
  # @api private
62
62
  class RenderCollectionNormalizer < RenderNormalizer
63
63
  register 'render_collection.action_view'
64
- TYPE = 'template.view.collection'.freeze
64
+ TYPE = 'template.view.collection'
65
65
 
66
66
  def normalize(_transaction, _name, payload)
67
67
  normalize_render(payload, TYPE)
@@ -20,7 +20,8 @@ module ElasticAPM
20
20
  return :skip if %w[SCHEMA CACHE].include?(payload[:name])
21
21
 
22
22
  name = summarize(payload[:sql]) || payload[:name]
23
- context = Span::Context.new(statement: payload[:sql], type: 'sql')
23
+ context =
24
+ Span::Context.new(db: { statement: payload[:sql], type: 'sql' })
24
25
  [name, @type, context]
25
26
  end
26
27
 
@@ -26,7 +26,7 @@ module ElasticAPM
26
26
  app.middleware.insert 0, Middleware
27
27
  end
28
28
  rescue StandardError => e
29
- Rails.logger.error "#{Log::PREFIX}Failed to start: #{e.message}"
29
+ Rails.logger.error "#{Logging::PREFIX}Failed to start: #{e.message}"
30
30
  Rails.logger.debug e.backtrace.join("\n")
31
31
  end
32
32
  end
@@ -7,61 +7,82 @@ require 'elastic_apm/span/context'
7
7
  module ElasticAPM
8
8
  # @api private
9
9
  class Span
10
- DEFAULT_TYPE = 'custom'.freeze
10
+ DEFAULT_TYPE = 'custom'
11
11
 
12
12
  # rubocop:disable Metrics/ParameterLists
13
13
  def initialize(
14
- transaction,
15
- id,
16
14
  name,
17
15
  type = nil,
16
+ transaction: nil,
18
17
  parent: nil,
19
- context: nil
18
+ context: nil,
19
+ stacktrace_builder: nil
20
20
  )
21
- @transaction = transaction
22
- @id = id
23
21
  @name = name
24
22
  @type = type || DEFAULT_TYPE
25
- @parent = parent
26
- @context = context
27
23
 
28
- @stacktrace = nil
29
- @original_backtrace = nil
24
+ @id = SecureRandom.hex(8)
25
+
26
+ self.transaction = transaction
27
+ self.parent = parent
28
+
29
+ @context = context
30
+ @stacktrace_builder = stacktrace_builder
30
31
  end
31
32
  # rubocop:enable Metrics/ParameterLists
32
33
 
33
- attr_accessor :name, :type, :original_backtrace
34
- attr_reader :id, :context, :stacktrace, :duration, :parent, :relative_start
34
+ attr_accessor :name, :type, :original_backtrace, :parent
35
+ attr_reader :id, :context, :stacktrace, :duration,
36
+ :relative_start, :timestamp, :transaction_id, :trace_id
37
+
38
+ def transaction=(transaction)
39
+ @transaction_id = transaction&.id
40
+ @timestamp = transaction&.timestamp
41
+ @trace_id = transaction&.trace_id
42
+ end
43
+
44
+ def parent_id
45
+ @parent&.id || transaction_id
46
+ end
47
+
48
+ # life cycle
35
49
 
36
50
  def start
37
- @relative_start = Util.micros - @transaction.timestamp
51
+ raise 'Transaction needed to start span' unless transaction_id
52
+
53
+ @relative_start = Util.micros - timestamp
38
54
 
39
55
  self
40
56
  end
41
57
 
58
+ def stop
59
+ @duration = Util.micros - timestamp - relative_start
60
+ end
61
+
42
62
  def done
43
- @duration = Util.micros - @transaction.timestamp - relative_start
63
+ stop
44
64
 
45
- if original_backtrace && long_enough_for_stacktrace?
46
- @stacktrace =
47
- @transaction.instrumenter.agent.stacktrace_builder.build(
48
- original_backtrace, type: :span
49
- )
65
+ if should_build_stacktrace?
66
+ build_stacktrace
50
67
  end
51
68
 
52
- self.original_backtrace = nil # release it
53
-
54
69
  self
55
70
  end
56
71
 
57
- def done?
72
+ def stopped?
58
73
  !!duration
59
74
  end
60
75
 
76
+ def started?
77
+ !!relative_start
78
+ end
79
+
61
80
  def running?
62
- relative_start && !done?
81
+ started? && !stopped?
63
82
  end
64
83
 
84
+ # relations
85
+
65
86
  def inspect
66
87
  "<ElasticAPM::Span id:#{id}" \
67
88
  " name:#{name.inspect}" \
@@ -71,14 +92,23 @@ module ElasticAPM
71
92
 
72
93
  private
73
94
 
95
+ def should_build_stacktrace?
96
+ @stacktrace_builder && original_backtrace && long_enough_for_stacktrace?
97
+ end
98
+
99
+ def build_stacktrace
100
+ @stacktrace = @stacktrace_builder.build(original_backtrace, type: :span)
101
+ self.original_backtrace = nil # release it
102
+ end
103
+
74
104
  def long_enough_for_stacktrace?
75
- min_duration = @transaction.instrumenter.config.span_frames_min_duration
105
+ min_duration =
106
+ @stacktrace_builder.config.span_frames_min_duration_us
76
107
 
77
- case min_duration
78
- when -1 then true
79
- when 0 then false
80
- else duration / 1000 >= min_duration
81
- end
108
+ return true if min_duration < 0
109
+ return false if min_duration == 0
110
+
111
+ duration >= min_duration
82
112
  end
83
113
  end
84
114
  end
@@ -6,13 +6,39 @@ module ElasticAPM
6
6
  class Context
7
7
  include NaivelyHashable
8
8
 
9
- def initialize(args)
10
- args.each do |key, val|
11
- send(:"#{key}=", val)
9
+ def initialize(db: nil, http: nil)
10
+ @db = db && Db.new(db)
11
+ @http = http && Http.new(http)
12
+ end
13
+
14
+ attr_accessor :sync, :db, :http
15
+
16
+ # @api private
17
+ class Db
18
+ include NaivelyHashable
19
+
20
+ def initialize(instance: nil, statement: nil, type: nil, user: nil)
21
+ @instance = instance
22
+ @statement = statement
23
+ @type = type
24
+ @user = user
12
25
  end
26
+
27
+ attr_accessor :instance, :statement, :type, :user
13
28
  end
14
29
 
15
- attr_accessor :instance, :statement, :type, :user
30
+ # @api private
31
+ class Http
32
+ include NaivelyHashable
33
+
34
+ def initialize(url: nil, status_code: nil, method: nil)
35
+ @url = url
36
+ @status_code = status_code
37
+ @method = method
38
+ end
39
+
40
+ attr_accessor :url, :status_code, :method
41
+ end
16
42
  end
17
43
  end
18
44
  end