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,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ class Config
5
+ # @api private
6
+ class Duration
7
+ MULTIPLIERS = { 'ms' => 0.001, 'm' => 60 }.freeze
8
+ REGEX = /^(-)?(\d+)(m|ms|s)?$/i.freeze
9
+
10
+ def initialize(seconds)
11
+ @seconds = seconds
12
+ end
13
+
14
+ attr_accessor :seconds
15
+
16
+ def self.parse(str, default_unit:)
17
+ _, negative, amount, unit = REGEX.match(str).to_a
18
+ unit ||= default_unit
19
+ seconds = MULTIPLIERS.fetch(unit.downcase, 1) * amount.to_i
20
+ seconds = 0 - seconds if negative
21
+ new(seconds)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ class Config
5
+ # @api private
6
+ class Size
7
+ MULTIPLIERS = {
8
+ 'kb' => 1024,
9
+ 'mb' => 1024 * 1_000,
10
+ 'gb' => 1024 * 100_000
11
+ }.freeze
12
+ REGEX = /^(\d+)(b|kb|mb|gb)?$/i.freeze
13
+
14
+ def initialize(bytes)
15
+ @bytes = bytes
16
+ end
17
+
18
+ attr_accessor :bytes
19
+
20
+ def self.parse(str, default_unit:)
21
+ _, amount, unit = REGEX.match(str).to_a
22
+ unit ||= default_unit
23
+ bytes = MULTIPLIERS.fetch(unit.downcase, 1) * amount.to_i
24
+ new(bytes)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ElasticAPM
4
+ # TODO: Move to txn.add_request ?
4
5
  # @api private
5
6
  class ContextBuilder
6
7
  def initialize(_agent); end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ module Deprecations
6
+ def deprecate(name, replacement = nil)
7
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
8
+ alias :"#{name}__deprecated" :"#{name}"
9
+
10
+ def #{name}(*args, &block)
11
+ warn "[ElasticAPM] [DEPRECATED] `#{name}' is being removed. " \
12
+ "#{replacement && "See `#{replacement}'."}" \
13
+ "\nCalled from \#{caller.first}"
14
+ #{name}__deprecated(*args, &block)
15
+ end
16
+ RUBY
17
+ end
18
+ end
19
+ end
@@ -9,16 +9,19 @@ module ElasticAPM
9
9
  # @api private
10
10
  class Error
11
11
  def initialize(culprit: nil)
12
- @id = SecureRandom.uuid
12
+ @id = SecureRandom.hex(16)
13
+ @trace_id = nil
13
14
  @culprit = culprit
14
15
 
15
16
  @timestamp = Util.micros
16
17
  @context = Context.new
17
18
 
18
19
  @transaction_id = nil
20
+ @parent_id = nil
19
21
  end
20
22
 
21
- attr_accessor :id, :culprit, :exception, :log, :transaction_id, :context
23
+ attr_accessor :id, :culprit, :exception, :log, :transaction_id, :context,
24
+ :parent_id, :trace_id
22
25
  attr_reader :timestamp
23
26
  end
24
27
  end
@@ -4,7 +4,7 @@ module ElasticAPM
4
4
  class Error
5
5
  # @api private
6
6
  class Exception
7
- MOD_SPLIT = '::'.freeze
7
+ MOD_SPLIT = '::'
8
8
 
9
9
  def initialize(exception, **attrs)
10
10
  @message =
@@ -7,6 +7,7 @@ module ElasticAPM
7
7
  @agent = agent
8
8
  end
9
9
 
10
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
10
11
  def build_exception(exception, handled: true)
11
12
  error = Error.new
12
13
  error.exception = Error::Exception.new(exception, handled: handled)
@@ -19,10 +20,14 @@ module ElasticAPM
19
20
 
20
21
  if (transaction = ElasticAPM.current_transaction)
21
22
  error.context = transaction.context.dup
23
+ error.trace_id = transaction.trace_id
24
+ error.transaction_id = transaction.id
25
+ error.parent_id = ElasticAPM.current_span&.id || transaction.id
22
26
  end
23
27
 
24
28
  error
25
29
  end
30
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
26
31
 
27
32
  def build_log(message, backtrace: nil, **attrs)
28
33
  error = Error.new
@@ -4,41 +4,57 @@ require 'elastic_apm/span'
4
4
  require 'elastic_apm/transaction'
5
5
 
6
6
  module ElasticAPM
7
+ # rubocop:disable Metrics/ClassLength
7
8
  # @api private
8
9
  class Instrumenter
9
- include Log
10
+ include Logging
10
11
 
11
- KEY = :__elastic_transaction_key
12
+ TRANSACTION_KEY = :__elastic_transaction_key
13
+ SPAN_KEY = :__elastic_span_key
12
14
 
13
15
  # @api private
14
- class TransactionInfo
16
+ class Current
15
17
  def initialize
16
- self.current = nil
18
+ self.transaction = nil
19
+ self.span = nil
17
20
  end
18
21
 
19
- def current
20
- Thread.current[KEY]
22
+ def transaction
23
+ Thread.current[TRANSACTION_KEY]
21
24
  end
22
25
 
23
- def current=(transaction)
24
- Thread.current[KEY] = transaction
26
+ def transaction=(transaction)
27
+ Thread.current[TRANSACTION_KEY] = transaction
28
+ end
29
+
30
+ def span
31
+ Thread.current[SPAN_KEY]
32
+ end
33
+
34
+ def span=(span)
35
+ Thread.current[SPAN_KEY] = span
25
36
  end
26
37
  end
27
38
 
28
- def initialize(agent)
29
- @agent = agent
30
- @config = agent.config
39
+ def initialize(config, &enqueue)
40
+ @config = config
41
+ @enqueue = enqueue
31
42
 
32
- @transaction_info = TransactionInfo.new
43
+ @current = Current.new
33
44
  end
34
45
 
35
- attr_reader :agent, :config, :pending_transactions
46
+ attr_reader :config, :enqueue
36
47
 
37
48
  def start
49
+ debug 'Starting instrumenter'
38
50
  end
39
51
 
40
52
  def stop
41
- current_transaction.release if current_transaction
53
+ debug 'Stopping instrumenter'
54
+
55
+ self.current_transaction = nil
56
+ self.current_span = nil
57
+
42
58
  @subscriber.unregister! if @subscriber
43
59
  end
44
60
 
@@ -47,70 +63,118 @@ module ElasticAPM
47
63
  @subscriber.register!
48
64
  end
49
65
 
66
+ # transactions
67
+
50
68
  def current_transaction
51
- @transaction_info.current
69
+ @current.transaction
52
70
  end
53
71
 
54
72
  def current_transaction=(transaction)
55
- @transaction_info.current = transaction
73
+ @current.transaction = transaction
56
74
  end
57
75
 
58
76
  # rubocop:disable Metrics/MethodLength
59
- # rubocop:disable Metrics/CyclomaticComplexity
60
- def transaction(name = nil, type = nil, context: nil, sampled: nil)
61
- unless config.instrument
62
- yield if block_given?
63
- return
64
- end
77
+ def start_transaction(
78
+ name = nil,
79
+ type = nil,
80
+ context: nil,
81
+ traceparent: nil
82
+ )
83
+ return nil unless config.instrument?
65
84
 
66
85
  if (transaction = current_transaction)
67
- yield transaction if block_given?
68
- return transaction
86
+ raise ExistingTransactionError,
87
+ "Transactions may not be nested.\nAlready inside #{transaction}"
69
88
  end
70
89
 
71
- sampled = random_sample? if sampled.nil?
90
+ sampled = traceparent ? traceparent.recorded? : random_sample?
72
91
 
73
92
  transaction =
74
- Transaction.new self, name, type, context: context, sampled: sampled
93
+ Transaction.new(
94
+ name,
95
+ type,
96
+ context: context,
97
+ traceparent: traceparent,
98
+ sampled: sampled
99
+ )
100
+
101
+ transaction.start
75
102
 
76
103
  self.current_transaction = transaction
77
- return transaction unless block_given?
104
+ end
105
+ # rubocop:enable Metrics/MethodLength
78
106
 
79
- begin
80
- yield transaction
81
- ensure
82
- self.current_transaction = nil
83
- transaction.done
84
- end
107
+ def end_transaction(result = nil)
108
+ return nil unless (transaction = current_transaction)
109
+
110
+ self.current_transaction = nil
111
+
112
+ transaction.done result
113
+
114
+ enqueue.call transaction
85
115
 
86
116
  transaction
87
117
  end
88
- # rubocop:enable Metrics/CyclomaticComplexity
89
- # rubocop:enable Metrics/MethodLength
90
118
 
91
- def random_sample?
92
- rand <= config.transaction_sample_rate
119
+ # spans
120
+
121
+ def current_span
122
+ @current.span
93
123
  end
94
124
 
95
- # rubocop:disable Metrics/MethodLength
96
- def span(name, type = nil, backtrace: nil, context: nil, &block)
97
- unless current_transaction
98
- return yield if block_given?
125
+ def current_span=(span)
126
+ @current.span = span
127
+ end
128
+
129
+ # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
130
+ def start_span(name, type = nil, backtrace: nil, context: nil)
131
+ return unless (transaction = current_transaction)
132
+ return unless transaction.sampled?
133
+
134
+ transaction.inc_started_spans!
135
+
136
+ if transaction.max_spans_reached?(config)
137
+ transaction.inc_dropped_spans!
99
138
  return
100
139
  end
101
140
 
102
- current_transaction.span(
141
+ span = Span.new(
103
142
  name,
104
143
  type,
105
- backtrace: backtrace,
106
- context: context,
107
- &block
144
+ transaction: transaction,
145
+ parent: current_span || transaction,
146
+ context: context
108
147
  )
148
+
149
+ if backtrace && span_frames_min_duration?
150
+ span.original_backtrace = backtrace
151
+ end
152
+
153
+ self.current_span = span
154
+
155
+ span.start
156
+ end
157
+ # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
158
+
159
+ def end_span
160
+ return unless (span = current_span)
161
+
162
+ span.done
163
+
164
+ self.current_span =
165
+ span.parent&.is_a?(Span) && span.parent || nil
166
+
167
+ enqueue.call span
168
+
169
+ span
109
170
  end
110
- # rubocop:enable Metrics/MethodLength
171
+
172
+ # metadata
111
173
 
112
174
  def set_tag(key, value)
113
175
  return unless current_transaction
176
+
177
+ key = key.to_s.gsub(/[\."\*]/, '_').to_sym
114
178
  current_transaction.context.tags[key] = value.to_s
115
179
  end
116
180
 
@@ -124,17 +188,21 @@ module ElasticAPM
124
188
  current_transaction.context.user = Context::User.new(config, user)
125
189
  end
126
190
 
127
- def submit_transaction(transaction)
128
- agent.enqueue_transaction transaction
129
-
130
- return unless config.debug_transactions
131
- debug('Submitted transaction:') { Util.inspect_transaction transaction }
132
- end
133
-
134
191
  def inspect
135
192
  '<ElasticAPM::Instrumenter ' \
136
193
  "current_transaction=#{current_transaction.inspect}" \
137
194
  '>'
138
195
  end
196
+
197
+ private
198
+
199
+ def random_sample?
200
+ rand <= config.transaction_sample_rate
201
+ end
202
+
203
+ def span_frames_min_duration?
204
+ config.span_frames_min_duration != 0
205
+ end
139
206
  end
207
+ # rubocop:enable Metrics/ClassLength
140
208
  end
@@ -2,4 +2,5 @@
2
2
 
3
3
  module ElasticAPM
4
4
  class InternalError < StandardError; end
5
+ class ExistingTransactionError < InternalError; end
5
6
  end
@@ -1,9 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'logger'
4
+
3
5
  module ElasticAPM
4
6
  # @api private
5
- module Log
6
- PREFIX = '[ElasticAPM] '.freeze
7
+ module Logging
8
+ PREFIX = '[ElasticAPM] '
9
+
10
+ LEVELS = {
11
+ debug: Logger::DEBUG,
12
+ info: Logger::INFO,
13
+ warn: Logger::WARN,
14
+ error: Logger::ERROR,
15
+ fatal: Logger::FATAL
16
+ }.freeze
7
17
 
8
18
  def debug(msg, *args, &block)
9
19
  log(:debug, msg, *args, &block)
@@ -25,26 +35,21 @@ module ElasticAPM
25
35
  log(:fatal, msg, *args, &block)
26
36
  end
27
37
 
38
+ private
39
+
28
40
  def log(lvl, msg, *args)
29
- return unless logger
41
+ return unless (logger = @config&.logger)
42
+ return unless LEVELS[lvl] >= (@config&.log_level || 0)
30
43
 
31
44
  formatted_msg = prepend_prefix(format(msg.to_s, *args))
32
45
 
33
46
  return logger.send(lvl, formatted_msg) unless block_given?
34
47
 
35
- # TODO: dont evaluate block if level is higher
36
48
  logger.send(lvl, "#{formatted_msg}\n#{yield}")
37
49
  end
38
50
 
39
- private
40
-
41
51
  def prepend_prefix(str)
42
52
  "#{PREFIX}#{str}"
43
53
  end
44
-
45
- def logger
46
- return false unless (config = instance_variable_get(:@config))
47
- config.logger
48
- end
49
54
  end
50
55
  end