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
@@ -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