elastic-apm 0.6.2 → 0.7.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rubocop.yml +3 -0
  4. data/Gemfile +0 -3
  5. data/elastic-apm.gemspec +1 -2
  6. data/lib/elastic_apm/agent.rb +13 -8
  7. data/lib/elastic_apm/config.rb +7 -6
  8. data/lib/elastic_apm/context_builder.rb +1 -1
  9. data/lib/elastic_apm/error_builder.rb +13 -5
  10. data/lib/elastic_apm/instrumenter.rb +10 -9
  11. data/lib/elastic_apm/middleware.rb +4 -3
  12. data/lib/elastic_apm/normalizers/active_record.rb +2 -2
  13. data/lib/elastic_apm/railtie.rb +10 -3
  14. data/lib/elastic_apm/{injectors.rb → spies.rb} +17 -17
  15. data/lib/elastic_apm/{injectors → spies}/action_dispatch.rb +3 -3
  16. data/lib/elastic_apm/{injectors → spies}/delayed_job.rb +4 -4
  17. data/lib/elastic_apm/{injectors → spies}/elasticsearch.rb +3 -3
  18. data/lib/elastic_apm/{injectors → spies}/json.rb +3 -3
  19. data/lib/elastic_apm/{injectors → spies}/mongo.rb +9 -5
  20. data/lib/elastic_apm/{injectors → spies}/net_http.rb +3 -3
  21. data/lib/elastic_apm/{injectors → spies}/redis.rb +3 -3
  22. data/lib/elastic_apm/{injectors → spies}/sequel.rb +4 -4
  23. data/lib/elastic_apm/{injectors → spies}/sidekiq.rb +10 -7
  24. data/lib/elastic_apm/{injectors → spies}/sinatra.rb +4 -4
  25. data/lib/elastic_apm/{injectors → spies}/tilt.rb +3 -3
  26. data/lib/elastic_apm/sql_summarizer.rb +10 -5
  27. data/lib/elastic_apm/stacktrace.rb +1 -105
  28. data/lib/elastic_apm/stacktrace/frame.rb +1 -5
  29. data/lib/elastic_apm/stacktrace_builder.rb +98 -0
  30. data/lib/elastic_apm/subscriber.rb +2 -3
  31. data/lib/elastic_apm/timed_worker.rb +4 -0
  32. data/lib/elastic_apm/transaction.rb +6 -2
  33. data/lib/elastic_apm/util/inflector.rb +45 -11
  34. data/lib/elastic_apm/version.rb +1 -1
  35. metadata +16 -30
  36. data/lib/elastic_apm/stacktrace/line_cache.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e3f8d9490f60e28d99b6e15e7efa2823fb773bacbdc9762f91fc92a4214b05b
4
- data.tar.gz: 655814ff9046d8e9dd2d586688b9bc45acdb95f0102626af5ad0886330b58c16
3
+ metadata.gz: '09106c431ff50c403a81c2013f49d9b6dfb17a23fefd88a1643919322dcf4ebe'
4
+ data.tar.gz: 4d3bde4e2bbd1de3dba89e992cd4ff032ad8580b82bce0b3d22eed5ce7c11f12
5
5
  SHA512:
6
- metadata.gz: d4b9cace9b89eddb0ec281061fd098daa1e9297feb7fbc1c5de8ba44746ad2ab225df877480b5a213847d65502e39c321a79d4f35bee257d60d4a63a254c76e5
7
- data.tar.gz: ae713fe3ad97666862ed91ecaeb8a199cfdaacb69edeb011840bb34e7da7692e97dffe15b729e3ec1275663bf2e381f241cb7579ca366a1ac7224c7465cbe9db
6
+ metadata.gz: f2f6b1e3e0da8311d54801cc8f9e84f2e9d6b119652a97e86549fdb9ddc03cab252830507c8c2489fbddb77e2903139490c710753f29a2f40357c65803575d53
7
+ data.tar.gz: 18050a76699c39f5cda9783a4c8eefb4443d2eefef61f5eaaf5107128f54806a34e6460271c63076425235eb86f408ca6663c9609a6547e969ff30f8eda0e5c6
data/.rspec CHANGED
@@ -1,2 +1 @@
1
- --format documentation
2
1
  --color
@@ -53,5 +53,8 @@ Style/SafeNavigation:
53
53
  Style/DoubleNegation:
54
54
  Enabled: false
55
55
 
56
+ Style/EmptyMethod:
57
+ Enabled: false
58
+
56
59
  Rails/Delegate:
57
60
  Enabled: false
data/Gemfile CHANGED
@@ -38,6 +38,3 @@ when /.+/
38
38
  else
39
39
  gem framework
40
40
  end
41
-
42
- gem 'rails' if framework == 'sinatra'
43
- gem 'sinatra' if framework == 'rails'
@@ -14,10 +14,9 @@ Gem::Specification.new do |spec|
14
14
  spec.license = 'Apache-2.0'
15
15
  spec.required_ruby_version = ">= 2.2.0"
16
16
 
17
- spec.add_dependency('activesupport', '>= 3.0.0')
18
-
19
17
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
20
18
  f.match(%r{^(test|spec|features)/})
21
19
  end
20
+
22
21
  spec.require_paths = ['lib']
23
22
  end
@@ -3,9 +3,10 @@
3
3
  require 'elastic_apm/naively_hashable'
4
4
  require 'elastic_apm/context_builder'
5
5
  require 'elastic_apm/error_builder'
6
+ require 'elastic_apm/stacktrace_builder'
6
7
  require 'elastic_apm/error'
7
8
  require 'elastic_apm/http'
8
- require 'elastic_apm/injectors'
9
+ require 'elastic_apm/spies'
9
10
  require 'elastic_apm/serializers'
10
11
  require 'elastic_apm/timed_worker'
11
12
 
@@ -58,26 +59,28 @@ module ElasticAPM
58
59
 
59
60
  def initialize(config)
60
61
  @config = config
62
+ @http = Http.new(config)
61
63
 
62
64
  @messages = Queue.new
63
65
  @pending_transactions = Queue.new
64
- @http = Http.new(config)
65
66
 
66
- @instrumenter = Instrumenter.new(config, self)
67
- @context_builder = ContextBuilder.new(config)
68
- @error_builder = ErrorBuilder.new(config)
67
+ @instrumenter = Instrumenter.new(self)
68
+
69
+ @context_builder = ContextBuilder.new(self)
70
+ @error_builder = ErrorBuilder.new(self)
71
+ @stacktrace_builder = StacktraceBuilder.new(self)
69
72
  end
70
73
 
71
74
  attr_reader :config, :messages, :pending_transactions, :instrumenter,
72
- :context_builder, :http
75
+ :context_builder, :stacktrace_builder, :http
73
76
 
74
77
  def start
75
78
  debug '[%s] Starting agent, reporting to %s', VERSION, config.server_url
76
79
 
77
80
  @instrumenter.start
78
81
 
79
- config.enabled_injectors.each do |lib|
80
- require "elastic_apm/injectors/#{lib}"
82
+ config.enabled_spies.each do |lib|
83
+ require "elastic_apm/spies/#{lib}"
81
84
  end
82
85
 
83
86
  self
@@ -95,6 +98,8 @@ module ElasticAPM
95
98
  stop
96
99
  end
97
100
 
101
+ # queues
102
+
98
103
  def enqueue_transaction(transaction)
99
104
  boot_worker unless worker_running?
100
105
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'logger'
4
+ require 'yaml'
4
5
 
5
6
  module ElasticAPM
6
7
  # rubocop:disable Metrics/ClassLength
@@ -36,7 +37,7 @@ module ElasticAPM
36
37
  source_lines_error_library_frames: 0,
37
38
  source_lines_span_library_frames: 0,
38
39
 
39
- disabled_injectors: %w[json],
40
+ disabled_spies: %w[json],
40
41
 
41
42
  current_user_id_method: :id,
42
43
  current_user_email_method: :email,
@@ -74,7 +75,7 @@ module ElasticAPM
74
75
  'ELASTIC_APM_VERIFY_SERVER_CERT' => [:bool, 'verify_server_cert'],
75
76
  'ELASTIC_APM_TRANSACTION_MAX_SPANS' => [:int, 'transaction_max_spans'],
76
77
 
77
- 'ELASTIC_APM_DISABLED_INJECTORS' => [:list, 'disabled_injectors']
78
+ 'ELASTIC_APM_DISABLED_SPIES' => [:list, 'disabled_spies']
78
79
  }.freeze
79
80
 
80
81
  def initialize(options = {})
@@ -123,7 +124,7 @@ module ElasticAPM
123
124
  attr_accessor :source_lines_error_library_frames
124
125
  attr_accessor :source_lines_span_library_frames
125
126
 
126
- attr_accessor :disabled_injectors
127
+ attr_accessor :disabled_spies
127
128
 
128
129
  attr_accessor :view_paths
129
130
  attr_accessor :root_path
@@ -170,7 +171,7 @@ module ElasticAPM
170
171
  end
171
172
 
172
173
  # rubocop:disable Metrics/MethodLength
173
- def available_injectors
174
+ def available_spies
174
175
  %w[
175
176
  action_dispatch
176
177
  delayed_job
@@ -187,8 +188,8 @@ module ElasticAPM
187
188
  end
188
189
  # rubocop:enable Metrics/MethodLength
189
190
 
190
- def enabled_injectors
191
- available_injectors - disabled_injectors
191
+ def enabled_spies
192
+ available_spies - disabled_spies
192
193
  end
193
194
 
194
195
  private
@@ -3,7 +3,7 @@
3
3
  module ElasticAPM
4
4
  # @api private
5
5
  class ContextBuilder
6
- def initialize(_config); end
6
+ def initialize(_agent); end
7
7
 
8
8
  def build(rack_env)
9
9
  context = Context.new
@@ -3,15 +3,18 @@
3
3
  module ElasticAPM
4
4
  # @api private
5
5
  class ErrorBuilder
6
- def initialize(config)
7
- @config = config
6
+ def initialize(agent)
7
+ @agent = agent
8
8
  end
9
9
 
10
10
  def build_exception(exception, handled: true)
11
11
  error = Error.new
12
12
  error.exception = Error::Exception.new(exception, handled: handled)
13
13
 
14
- add_stacktrace error, :exception, exception.backtrace
14
+ if exception.backtrace
15
+ add_stacktrace error, :exception, exception.backtrace
16
+ end
17
+
15
18
  add_transaction_id error
16
19
 
17
20
  if (transaction = ElasticAPM.current_transaction)
@@ -25,7 +28,10 @@ module ElasticAPM
25
28
  error = Error.new
26
29
  error.log = Error::Log.new(message, **attrs)
27
30
 
28
- add_stacktrace error, :log, backtrace
31
+ if backtrace
32
+ add_stacktrace error, :log, backtrace
33
+ end
34
+
29
35
  add_transaction_id error
30
36
 
31
37
  error
@@ -34,7 +40,9 @@ module ElasticAPM
34
40
  private
35
41
 
36
42
  def add_stacktrace(error, kind, backtrace)
37
- return unless (stacktrace = Stacktrace.build(@config, backtrace, :error))
43
+ stacktrace =
44
+ @agent.stacktrace_builder.build(backtrace, type: :error)
45
+ return unless stacktrace
38
46
 
39
47
  case kind
40
48
  when :exception
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'elastic_apm/subscriber'
4
3
  require 'elastic_apm/span'
5
4
  require 'elastic_apm/transaction'
6
5
 
@@ -26,24 +25,26 @@ module ElasticAPM
26
25
  end
27
26
  end
28
27
 
29
- def initialize(config, agent, subscriber_class: Subscriber)
30
- @config = config
28
+ def initialize(agent)
31
29
  @agent = agent
30
+ @config = agent.config
32
31
 
33
32
  @transaction_info = TransactionInfo.new
34
-
35
- @subscriber = subscriber_class.new(config)
36
33
  end
37
34
 
38
- attr_reader :config, :pending_transactions
35
+ attr_reader :agent, :config, :pending_transactions
39
36
 
40
37
  def start
41
- @subscriber.register!
42
38
  end
43
39
 
44
40
  def stop
45
41
  current_transaction.release if current_transaction
46
- @subscriber.unregister!
42
+ @subscriber.unregister! if @subscriber
43
+ end
44
+
45
+ def subscriber=(subscriber)
46
+ @subscriber = subscriber
47
+ @subscriber.register!
47
48
  end
48
49
 
49
50
  def current_transaction
@@ -112,7 +113,7 @@ module ElasticAPM
112
113
  end
113
114
 
114
115
  def submit_transaction(transaction)
115
- @agent.enqueue_transaction transaction
116
+ agent.enqueue_transaction transaction
116
117
 
117
118
  return unless config.debug_transactions
118
119
  debug('Submitted transaction:') { Util.inspect_transaction transaction }
@@ -10,20 +10,21 @@ module ElasticAPM
10
10
  # rubocop:disable Metrics/MethodLength
11
11
  def call(env)
12
12
  begin
13
- transaction = ElasticAPM.transaction 'Rack', 'app',
13
+ transaction = ElasticAPM.transaction 'Rack', 'request',
14
14
  context: ElasticAPM.build_context(env)
15
15
 
16
16
  resp = @app.call env
17
17
  status, headers, = resp
18
18
 
19
19
  if transaction
20
- transaction.submit(status, status: status, headers: headers)
20
+ result = "HTTP #{status.to_s[0]}xx"
21
+ transaction.submit(result, status: status, headers: headers)
21
22
  end
22
23
  rescue InternalError
23
24
  raise # Don't report ElasticAPM errors
24
25
  rescue ::Exception => e
25
26
  ElasticAPM.report(e, handled: false)
26
- transaction.submit(500, status: 500) if transaction
27
+ transaction.submit('HTTP 5xx', status: 500) if transaction
27
28
  raise
28
29
  ensure
29
30
  transaction.release if transaction
@@ -19,8 +19,8 @@ module ElasticAPM
19
19
  def normalize(_transaction, _name, payload)
20
20
  return :skip if %w[SCHEMA CACHE].include?(payload[:name])
21
21
 
22
- name = summarize(payload[:sql]) || payload[:name] || 'SQL'
23
- context = Span::Context.new(statement: payload[:sql])
22
+ name = summarize(payload[:sql]) || payload[:name]
23
+ context = Span::Context.new(statement: payload[:sql].to_s)
24
24
  [name, @type, context]
25
25
  end
26
26
 
@@ -1,18 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'elastic_apm/subscriber'
4
+
3
5
  module ElasticAPM
4
6
  # @api private
5
7
  class Railtie < Rails::Railtie
6
8
  config.elastic_apm = ActiveSupport::OrderedOptions.new
9
+
7
10
  Config::DEFAULTS.each { |option, value| config.elastic_apm[option] = value }
8
11
 
9
12
  initializer 'elastic_apm.initialize' do |app|
10
13
  config = app.config.elastic_apm.merge(app: app)
11
14
 
12
15
  begin
13
- ElasticAPM.start config
16
+ agent = ElasticAPM.start config
17
+
18
+ if agent
19
+ agent.instrumenter.subscriber = ElasticAPM::Subscriber.new(agent)
14
20
 
15
- app.middleware.insert 0, Middleware
21
+ app.middleware.insert 0, Middleware
22
+ end
16
23
  rescue StandardError => e
17
24
  Rails.logger.error "#{Log::PREFIX}Failed to start: #{e.message}"
18
25
  Rails.logger.debug e.backtrace.join("\n")
@@ -20,7 +27,7 @@ module ElasticAPM
20
27
  end
21
28
 
22
29
  config.after_initialize do
23
- require 'elastic_apm/injectors/action_dispatch'
30
+ require 'elastic_apm/spies/action_dispatch'
24
31
  end
25
32
  end
26
33
  end
@@ -4,20 +4,20 @@ require 'elastic_apm/util/inflector'
4
4
 
5
5
  module ElasticAPM
6
6
  # @api private
7
- module Injectors
7
+ module Spies
8
8
  # @api private
9
9
  class Registration
10
- def initialize(const_name, require_paths, injector)
10
+ extend Forwardable
11
+
12
+ def initialize(const_name, require_paths, spy)
11
13
  @const_name = const_name
12
14
  @require_paths = Array(require_paths)
13
- @injector = injector
15
+ @spy = spy
14
16
  end
15
17
 
16
- attr_reader :const_name, :require_paths, :injector
18
+ attr_reader :const_name, :require_paths
17
19
 
18
- def install
19
- injector.install
20
- end
20
+ def_delegator :@spy, :install
21
21
  end
22
22
 
23
23
  def self.require_hooks
@@ -31,9 +31,9 @@ module ElasticAPM
31
31
  def self.register(*args)
32
32
  registration = Registration.new(*args)
33
33
 
34
- if const_defined?(registration.const_name)
35
- installed[registration.const_name] = registration
34
+ if safe_defined?(registration.const_name)
36
35
  registration.install
36
+ installed[registration.const_name] = registration
37
37
  else
38
38
  register_require_hook registration
39
39
  end
@@ -47,7 +47,7 @@ module ElasticAPM
47
47
 
48
48
  def self.hook_into(name)
49
49
  return unless (registration = require_hooks[name])
50
- return unless const_defined?(registration.const_name)
50
+ return unless safe_defined?(registration.const_name)
51
51
 
52
52
  installed[registration.const_name] = registration
53
53
  registration.install
@@ -57,11 +57,8 @@ module ElasticAPM
57
57
  end
58
58
  end
59
59
 
60
- def self.const_defined?(const_name)
61
- const = Util::Inflector.constantize(const_name)
62
- !!const
63
- rescue NameError
64
- false
60
+ def self.safe_defined?(const_name)
61
+ Util::Inflector.safe_constantize(const_name)
65
62
  end
66
63
  end
67
64
  end
@@ -76,8 +73,11 @@ module Kernel
76
73
  res = require_without_apm(path)
77
74
 
78
75
  begin
79
- ElasticAPM::Injectors.hook_into(path)
80
- rescue ::Exception
76
+ ElasticAPM::Spies.hook_into(path)
77
+ rescue ::Exception => e
78
+ puts "Failed hooking into '#{path}'. Please report this at " \
79
+ 'github.com/elastic/apm-agent-ruby'
80
+ puts e.backtrace.join("\n")
81
81
  end
82
82
 
83
83
  res
@@ -2,9 +2,9 @@
2
2
 
3
3
  module ElasticAPM
4
4
  # @api private
5
- module Injectors
5
+ module Spies
6
6
  # @api private
7
- class ActionDispatchInjector
7
+ class ActionDispatchSpy
8
8
  def install
9
9
  ::ActionDispatch::ShowExceptions.class_eval do
10
10
  alias render_exception_without_apm render_exception
@@ -20,7 +20,7 @@ module ElasticAPM
20
20
  register(
21
21
  'ActionDispatch::ShowExceptions',
22
22
  'action_dispatch/show_exception',
23
- ActionDispatchInjector.new
23
+ ActionDispatchSpy.new
24
24
  )
25
25
  end
26
26
  end
@@ -2,9 +2,9 @@
2
2
 
3
3
  module ElasticAPM
4
4
  # @api private
5
- module Injectors
5
+ module Spies
6
6
  # @api private
7
- class DelayedJobInjector
7
+ class DelayedJobSpy
8
8
  CLASS_SEPARATOR = '.'.freeze
9
9
  METHOD_SEPARATOR = '#'.freeze
10
10
  TYPE = 'Delayed::Job'.freeze
@@ -14,7 +14,7 @@ module ElasticAPM
14
14
  alias invoke_job_without_apm invoke_job
15
15
 
16
16
  def invoke_job(*args, &block)
17
- ::ElasticAPM::Injectors::DelayedJobInjector
17
+ ::ElasticAPM::Spies::DelayedJobSpy
18
18
  .invoke_job(self, *args, &block)
19
19
  end
20
20
  end
@@ -62,7 +62,7 @@ module ElasticAPM
62
62
  register(
63
63
  'Delayed::Backend::Base',
64
64
  'delayed/backend/base',
65
- DelayedJobInjector.new
65
+ DelayedJobSpy.new
66
66
  )
67
67
  end
68
68
  end
@@ -2,9 +2,9 @@
2
2
 
3
3
  module ElasticAPM
4
4
  # @api private
5
- module Injectors
5
+ module Spies
6
6
  # @api private
7
- class ElasticsearchInjector
7
+ class ElasticsearchSpy
8
8
  NAME_FORMAT = '%s %s'.freeze
9
9
  TYPE = 'db.elasticsearch'.freeze
10
10
 
@@ -27,7 +27,7 @@ module ElasticAPM
27
27
  register(
28
28
  'Elasticsearch::Transport::Client',
29
29
  'elasticsearch-transport',
30
- ElasticsearchInjector.new
30
+ ElasticsearchSpy.new
31
31
  )
32
32
  end
33
33
  end
@@ -4,9 +4,9 @@ require 'elastic_apm/span_helpers'
4
4
 
5
5
  module ElasticAPM
6
6
  # @api private
7
- module Injectors
7
+ module Spies
8
8
  # @api private
9
- class JSONInjector
9
+ class JSONSpy
10
10
  def install
11
11
  ::JSON.class_eval do
12
12
  include SpanHelpers
@@ -17,6 +17,6 @@ module ElasticAPM
17
17
  end
18
18
  end
19
19
 
20
- register 'JSON', 'json', JSONInjector.new
20
+ register 'JSON', 'json', JSONSpy.new
21
21
  end
22
22
  end
@@ -2,9 +2,9 @@
2
2
 
3
3
  module ElasticAPM
4
4
  # @api private
5
- module Injectors
5
+ module Spies
6
6
  # @api private
7
- class MongoInjector
7
+ class MongoSpy
8
8
  def install
9
9
  ::Mongo::Monitoring::Global.subscribe(
10
10
  ::Mongo::Monitoring::COMMAND,
@@ -35,6 +35,8 @@ module ElasticAPM
35
35
  private
36
36
 
37
37
  def push_event(event)
38
+ return unless ElasticAPM.current_transaction
39
+
38
40
  ctx = Span::Context.new(
39
41
  instance: event.database_name,
40
42
  statement: nil,
@@ -46,12 +48,14 @@ module ElasticAPM
46
48
  end
47
49
 
48
50
  def pop_event(event)
49
- span = @events[event.operation_id]
50
- span.done
51
+ return unless ElasticAPM.current_transaction
52
+
53
+ span = @events.delete(event.operation_id)
54
+ span && span.done
51
55
  end
52
56
  end
53
57
  end
54
58
 
55
- register 'Mongo', 'mongo', MongoInjector.new
59
+ register 'Mongo', 'mongo', MongoSpy.new
56
60
  end
57
61
  end
@@ -2,9 +2,9 @@
2
2
 
3
3
  module ElasticAPM
4
4
  # @api private
5
- module Injectors
5
+ module Spies
6
6
  # @api private
7
- class NetHTTPInjector
7
+ class NetHTTPSpy
8
8
  # rubocop:disable Metrics/MethodLength
9
9
  def install
10
10
  Net::HTTP.class_eval do
@@ -32,6 +32,6 @@ module ElasticAPM
32
32
  # rubocop:enable Metrics/MethodLength
33
33
  end
34
34
 
35
- register 'Net::HTTP', 'net/http', NetHTTPInjector.new
35
+ register 'Net::HTTP', 'net/http', NetHTTPSpy.new
36
36
  end
37
37
  end
@@ -2,9 +2,9 @@
2
2
 
3
3
  module ElasticAPM
4
4
  # @api private
5
- module Injectors
5
+ module Spies
6
6
  # @api private
7
- class RedisInjector
7
+ class RedisSpy
8
8
  def install
9
9
  ::Redis::Client.class_eval do
10
10
  alias call_without_apm call
@@ -22,6 +22,6 @@ module ElasticAPM
22
22
  end
23
23
  end
24
24
 
25
- register 'Redis', 'redis', RedisInjector.new
25
+ register 'Redis', 'redis', RedisSpy.new
26
26
  end
27
27
  end
@@ -4,9 +4,9 @@ require 'elastic_apm/sql_summarizer'
4
4
 
5
5
  module ElasticAPM
6
6
  # @api private
7
- module Injectors
7
+ module Spies
8
8
  # @api private
9
- class SequelInjector
9
+ class SequelSpy
10
10
  TYPE = 'db.sequel.sql'.freeze
11
11
 
12
12
  def self.summarizer
@@ -25,7 +25,7 @@ module ElasticAPM
25
25
  return log_connection_yield_without_apm(sql, *args, &block)
26
26
  end
27
27
 
28
- summarizer = ElasticAPM::Injectors::SequelInjector.summarizer
28
+ summarizer = ElasticAPM::Spies::SequelSpy.summarizer
29
29
  name = summarizer.summarize sql
30
30
  context = Span::Context.new(
31
31
  statement: sql,
@@ -40,6 +40,6 @@ module ElasticAPM
40
40
  # rubocop:enable Metrics/MethodLength
41
41
  end
42
42
 
43
- register 'Sequel', 'sequel', SequelInjector.new
43
+ register 'Sequel', 'sequel', SequelSpy.new
44
44
  end
45
45
  end
@@ -2,9 +2,9 @@
2
2
 
3
3
  module ElasticAPM
4
4
  # @api private
5
- module Injectors
5
+ module Spies
6
6
  # @api private
7
- class SidekiqInjector
7
+ class SidekiqSpy
8
8
  ACTIVE_JOB_WRAPPER =
9
9
  'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper'.freeze
10
10
 
@@ -12,7 +12,7 @@ module ElasticAPM
12
12
  class Middleware
13
13
  # rubocop:disable Metrics/MethodLength
14
14
  def call(_worker, job, queue)
15
- name = SidekiqInjector.name_for(job)
15
+ name = SidekiqSpy.name_for(job)
16
16
  transaction = ElasticAPM.transaction(name, 'Sidekiq')
17
17
  ElasticAPM.set_tag(:queue, queue)
18
18
 
@@ -58,10 +58,13 @@ module ElasticAPM
58
58
 
59
59
  def start
60
60
  result = start_without_apm
61
- ElasticAPM.start # might already be running from railtie
62
61
 
63
- return result unless ElasticAPM.running?
64
- ElasticAPM.agent.config.logger = Sidekiq.logger
62
+ # Already running from Railtie if Rails
63
+ if ElasticAPM.running?
64
+ ElasticAPM.agent.config.logger = Sidekiq.logger
65
+ else
66
+ ElasticAPM.start
67
+ end
65
68
 
66
69
  result
67
70
  end
@@ -81,6 +84,6 @@ module ElasticAPM
81
84
  end
82
85
  end
83
86
 
84
- register 'Sidekiq', 'sidekiq', SidekiqInjector.new
87
+ register 'Sidekiq', 'sidekiq', SidekiqSpy.new
85
88
  end
86
89
  end
@@ -2,9 +2,9 @@
2
2
 
3
3
  module ElasticAPM
4
4
  # @api private
5
- module Injectors
5
+ module Spies
6
6
  # @api private
7
- class SinatraInjector
7
+ class SinatraSpy
8
8
  # rubocop:disable Metrics/MethodLength
9
9
  def install
10
10
  ::Sinatra::Base.class_eval do
@@ -34,8 +34,8 @@ module ElasticAPM
34
34
  # rubocop:enable Metrics/MethodLength
35
35
  end
36
36
 
37
- register 'Sinatra::Base', 'sinatra/base', SinatraInjector.new
37
+ register 'Sinatra::Base', 'sinatra/base', SinatraSpy.new
38
38
 
39
- require 'elastic_apm/injectors/tilt'
39
+ require 'elastic_apm/spies/tilt'
40
40
  end
41
41
  end
@@ -2,9 +2,9 @@
2
2
 
3
3
  module ElasticAPM
4
4
  # @api private
5
- module Injectors
5
+ module Spies
6
6
  # @api private
7
- class TiltInjector
7
+ class TiltSpy
8
8
  TYPE = 'template.tilt'.freeze
9
9
 
10
10
  def install
@@ -22,6 +22,6 @@ module ElasticAPM
22
22
  end
23
23
  end
24
24
 
25
- register 'Tilt::Template', 'tilt/template', TiltInjector.new
25
+ register 'Tilt::Template', 'tilt/template', TiltSpy.new
26
26
  end
27
27
  end
@@ -5,11 +5,16 @@ 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
10
+
8
11
  REGEXES = {
9
- /^SELECT .* FROM ([^ ]+)/i => 'SELECT FROM ',
10
- /^INSERT INTO ([^ ]+)/i => 'INSERT INTO ',
11
- /^UPDATE ([^ ]+)/i => 'UPDATE ',
12
- /^DELETE FROM ([^ ]+)/i => 'DELETE FROM '
12
+ /^BEGIN/i => 'BEGIN',
13
+ /^COMMIT/i => 'COMMIT',
14
+ /^SELECT .* FROM #{TABLE_REGEX}/i => 'SELECT FROM ',
15
+ /^INSERT INTO #{TABLE_REGEX}/i => 'INSERT INTO ',
16
+ /^UPDATE #{TABLE_REGEX}/i => 'UPDATE ',
17
+ /^DELETE FROM #{TABLE_REGEX}/i => 'DELETE FROM '
13
18
  }.freeze
14
19
 
15
20
  FORMAT = '%s%s'.freeze
@@ -24,7 +29,7 @@ module ElasticAPM
24
29
  if (match = sql.match(regex))
25
30
  break format(FORMAT, sig, match[1].gsub(/["']/, ''))
26
31
  end
27
- end
32
+ end || DEFAULT
28
33
  end
29
34
  end
30
35
  end
@@ -1,36 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'elastic_apm/stacktrace/frame'
4
- require 'elastic_apm/stacktrace/line_cache'
5
-
6
3
  module ElasticAPM
7
4
  # @api private
8
5
  class Stacktrace
9
- JAVA_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/
10
- RUBY_FORMAT = /^(.+?):(\d+)(?::in `(.+?)')?$/
11
-
12
- RUBY_VERS_REGEX = %r{ruby(/gems)?[-/](\d+\.)+\d}
13
- JRUBY_ORG_REGEX = %r{org/jruby}
14
-
15
- def initialize(backtrace)
16
- @backtrace = backtrace
17
- end
18
-
19
- attr_reader :frames
20
-
21
- def self.build(config, backtrace, type)
22
- return nil unless backtrace
23
-
24
- stack = new(backtrace)
25
- stack.build_frames(config, type)
26
- stack
27
- end
28
-
29
- def build_frames(config, type)
30
- @frames = @backtrace.map do |line|
31
- build_frame(config, line, type)
32
- end
33
- end
6
+ attr_accessor :frames
34
7
 
35
8
  def length
36
9
  frames.length
@@ -39,82 +12,5 @@ module ElasticAPM
39
12
  def to_a
40
13
  frames.map(&:to_h)
41
14
  end
42
-
43
- private
44
-
45
- def parse_line(line)
46
- ruby_match = line.match(RUBY_FORMAT)
47
-
48
- if ruby_match
49
- _, file, number, method = ruby_match.to_a
50
- file.sub!(/\.class$/, '.rb')
51
- module_name = nil
52
- else
53
- java_match = line.match(JAVA_FORMAT)
54
- _, module_name, method, file, number = java_match.to_a
55
- end
56
-
57
- [file, number, method, module_name]
58
- end
59
-
60
- def library_frame?(config, abs_path)
61
- return false unless abs_path
62
-
63
- if abs_path.start_with?(config.root_path)
64
- return true if abs_path.start_with?(config.root_path + '/vendor')
65
- return false
66
- end
67
-
68
- return true if abs_path.match(RUBY_VERS_REGEX)
69
- return true if abs_path.match(JRUBY_ORG_REGEX)
70
-
71
- false
72
- end
73
-
74
- class << self
75
- attr_accessor :frame_cache
76
- end
77
-
78
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
79
- def build_frame(config, line, type)
80
- # TODO: Eventually move this to agent 'context'
81
- self.class.frame_cache ||= Util::LruCache.new(2048) do |cache, keys|
82
- line, type = keys
83
- abs_path, lineno, function, _module_name = parse_line(line)
84
-
85
- frame = Frame.new
86
- frame.abs_path = abs_path
87
- frame.filename = strip_load_path(abs_path)
88
- frame.function = function
89
- frame.lineno = lineno.to_i
90
- frame.library_frame = library_frame?(config, abs_path)
91
-
92
- line_count =
93
- context_lines_for(config, type, library_frame: frame.library_frame)
94
- frame.build_context line_count
95
-
96
- cache[[line, type]] = frame
97
- end
98
-
99
- self.class.frame_cache[[line, type]]
100
- end
101
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
102
-
103
- def strip_load_path(path)
104
- return nil if path.nil?
105
-
106
- prefix =
107
- $LOAD_PATH
108
- .map(&:to_s)
109
- .select { |s| path.start_with?(s) }
110
- .max_by(&:length)
111
-
112
- prefix ? path[prefix.chomp(File::SEPARATOR).length + 1..-1] : path
113
- end
114
-
115
- def context_lines_for(config, type, library_frame:)
116
- key = "source_lines_#{type}_#{library_frame ? 'library' : 'app'}_frames"
117
- config.send(key.to_sym)
118
- end
119
15
  end
120
16
  end
@@ -38,11 +38,7 @@ module ElasticAPM
38
38
  private
39
39
 
40
40
  def read_lines(path, range)
41
- if (cached = LineCache.get(path, range))
42
- return cached
43
- end
44
-
45
- LineCache.set(path, range, File.readlines(path)[range]) || []
41
+ File.readlines(path)[range]
46
42
  rescue Errno::ENOENT
47
43
  []
48
44
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elastic_apm/stacktrace/frame'
4
+ require 'elastic_apm/util/lru_cache'
5
+
6
+ module ElasticAPM
7
+ # @api private
8
+ class StacktraceBuilder
9
+ JAVA_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/
10
+ RUBY_FORMAT = /^(.+?):(\d+)(?::in `(.+?)')?$/
11
+
12
+ RUBY_VERS_REGEX = %r{ruby(/gems)?[-/](\d+\.)+\d}
13
+ JRUBY_ORG_REGEX = %r{org/jruby}
14
+
15
+ def initialize(agent)
16
+ @config = agent.config
17
+ @cache = Util::LruCache.new(2048, &method(:build_frame))
18
+ end
19
+
20
+ attr_reader :config
21
+
22
+ def build(backtrace, type:)
23
+ Stacktrace.new.tap do |s|
24
+ s.frames = backtrace.map do |line|
25
+ @cache[[line, type]]
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
33
+ def build_frame(cache, keys)
34
+ line, type = keys
35
+ abs_path, lineno, function, _module_name = parse_line(line)
36
+
37
+ frame = Stacktrace::Frame.new
38
+ frame.abs_path = abs_path
39
+ frame.filename = strip_load_path(abs_path)
40
+ frame.function = function
41
+ frame.lineno = lineno.to_i
42
+ frame.library_frame = library_frame?(config, abs_path)
43
+
44
+ line_count =
45
+ context_lines_for(config, type, library_frame: frame.library_frame)
46
+ frame.build_context line_count
47
+
48
+ cache[[line, type]] = frame
49
+ end
50
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
51
+
52
+ def parse_line(line)
53
+ ruby_match = line.match(RUBY_FORMAT)
54
+
55
+ if ruby_match
56
+ _, file, number, method = ruby_match.to_a
57
+ file.sub!(/\.class$/, '.rb')
58
+ module_name = nil
59
+ else
60
+ java_match = line.match(JAVA_FORMAT)
61
+ _, module_name, method, file, number = java_match.to_a
62
+ end
63
+
64
+ [file, number, method, module_name]
65
+ end
66
+
67
+ def library_frame?(config, abs_path)
68
+ return false unless abs_path
69
+
70
+ if abs_path.start_with?(config.root_path)
71
+ return true if abs_path.start_with?(config.root_path + '/vendor')
72
+ return false
73
+ end
74
+
75
+ return true if abs_path.match(RUBY_VERS_REGEX)
76
+ return true if abs_path.match(JRUBY_ORG_REGEX)
77
+
78
+ false
79
+ end
80
+
81
+ def strip_load_path(path)
82
+ return nil if path.nil?
83
+
84
+ prefix =
85
+ $LOAD_PATH
86
+ .map(&:to_s)
87
+ .select { |s| path.start_with?(s) }
88
+ .max_by(&:length)
89
+
90
+ prefix ? path[prefix.chomp(File::SEPARATOR).length + 1..-1] : path
91
+ end
92
+
93
+ def context_lines_for(config, type, library_frame:)
94
+ key = "source_lines_#{type}_#{library_frame ? 'library' : 'app'}_frames"
95
+ config.send(key.to_sym)
96
+ end
97
+ end
98
+ end
@@ -8,10 +8,9 @@ module ElasticAPM
8
8
  class Subscriber
9
9
  include Log
10
10
 
11
- def initialize(config, agent: ElasticAPM)
12
- @config = config
11
+ def initialize(agent)
13
12
  @agent = agent
14
- @normalizers = Normalizers.build(config)
13
+ @normalizers = Normalizers.build(agent.config)
15
14
  end
16
15
 
17
16
  def register!
@@ -75,6 +75,7 @@ module ElasticAPM
75
75
  @adapter.post('/v1/errors', payload)
76
76
  end
77
77
 
78
+ # rubocop:disable Metrics/MethodLength
78
79
  def collect_and_send_transactions
79
80
  return if pending_transactions.empty?
80
81
 
@@ -89,7 +90,10 @@ module ElasticAPM
89
90
  debug e.backtrace.join("\n")
90
91
  nil
91
92
  end
93
+
94
+ @last_sent_transactions = Time.now
92
95
  end
96
+ # rubocop:enable Metrics/MethodLength
93
97
 
94
98
  def collect_batched_transactions
95
99
  batch = []
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ElasticAPM
4
+ # rubocop:disable Metrics/ClassLength
4
5
  # @api private
5
6
  class Transaction
6
7
  DEFAULT_TYPE = 'custom'.freeze
@@ -103,8 +104,10 @@ module ElasticAPM
103
104
  span = next_span(name, type, context)
104
105
  spans << span
105
106
 
106
- span.stacktrace =
107
- backtrace && Stacktrace.build(@instrumenter.config, backtrace, :span)
107
+ if backtrace
108
+ span.stacktrace =
109
+ @instrumenter.agent.stacktrace_builder.build(backtrace, type: :span)
110
+ end
108
111
 
109
112
  span.start
110
113
  end
@@ -141,4 +144,5 @@ module ElasticAPM
141
144
  )
142
145
  end
143
146
  end
147
+ # rubocop:enable Metrics/ClassLength
144
148
  end
@@ -1,14 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ElasticAPM
4
+ # rubocop:disable all
2
5
  module Util
3
- # @api private
6
+ # From https://github.com/rails/rails/blob/v5.2.0/activesupport/lib/active_support/inflector/methods.rb#L254-L332
4
7
  module Inflector
5
- # rubocop:disable all
6
- # From https://github.com/rails/rails/blob/861b70e92f4a1fc0e465ffcf2ee62680519c8f6f/activesupport/lib/active_support/inflector/methods.rb#L249
8
+ extend self
9
+
7
10
  #
8
11
  # Tries to find a constant with the name specified in the argument string.
9
12
  #
10
- # 'Module'.constantize # => Module
11
- # 'Test::Unit'.constantize # => Test::Unit
13
+ # constantize('Module') # => Module
14
+ # constantize('Foo::Bar') # => Foo::Bar
12
15
  #
13
16
  # The name is assumed to be the one of a top-level constant, no matter
14
17
  # whether it starts with "::" or not. No lexical context is taken into
@@ -17,14 +20,14 @@ module ElasticAPM
17
20
  # C = 'outside'
18
21
  # module M
19
22
  # C = 'inside'
20
- # C # => 'inside'
21
- # 'C'.constantize # => 'outside', same as ::C
23
+ # C # => 'inside'
24
+ # constantize('C') # => 'outside', same as ::C
22
25
  # end
23
26
  #
24
27
  # NameError is raised when the name is not in CamelCase or the constant is
25
28
  # unknown.
26
- def self.constantize(camel_cased_word)
27
- names = camel_cased_word.split('::')
29
+ def constantize(camel_cased_word)
30
+ names = camel_cased_word.split("::".freeze)
28
31
 
29
32
  # Trigger a built-in NameError exception including the ill-formed constant in the message.
30
33
  Object.const_get(camel_cased_word) if names.empty?
@@ -42,7 +45,7 @@ module ElasticAPM
42
45
 
43
46
  # Go down the ancestors to check if it is owned directly. The check
44
47
  # stops when we reach Object or the end of ancestors tree.
45
- constant = constant.ancestors.inject do |const, ancestor|
48
+ constant = constant.ancestors.inject(constant) do |const, ancestor|
46
49
  break const if ancestor == Object
47
50
  break ancestor if ancestor.const_defined?(name, false)
48
51
  const
@@ -53,7 +56,38 @@ module ElasticAPM
53
56
  end
54
57
  end
55
58
  end
56
- # rubocop:enable all
59
+
60
+ # Tries to find a constant with the name specified in the argument string.
61
+ #
62
+ # safe_constantize('Module') # => Module
63
+ # safe_constantize('Foo::Bar') # => Foo::Bar
64
+ #
65
+ # The name is assumed to be the one of a top-level constant, no matter
66
+ # whether it starts with "::" or not. No lexical context is taken into
67
+ # account:
68
+ #
69
+ # C = 'outside'
70
+ # module M
71
+ # C = 'inside'
72
+ # C # => 'inside'
73
+ # safe_constantize('C') # => 'outside', same as ::C
74
+ # end
75
+ #
76
+ # +nil+ is returned when the name is not in CamelCase or the constant (or
77
+ # part of it) is unknown.
78
+ #
79
+ # safe_constantize('blargle') # => nil
80
+ # safe_constantize('UnknownModule') # => nil
81
+ # safe_constantize('UnknownModule::Foo::Bar') # => nil
82
+ def safe_constantize(camel_cased_word)
83
+ constantize(camel_cased_word)
84
+ rescue NameError => e
85
+ raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) ||
86
+ e.name.to_s == camel_cased_word.to_s)
87
+ rescue ArgumentError => e
88
+ raise unless /not missing constant #{const_regexp(camel_cased_word)}!$/.match?(e.message)
89
+ end
57
90
  end
58
91
  end
92
+ # rubocop:enable all
59
93
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ElasticAPM
4
- VERSION = '0.6.2'.freeze
4
+ VERSION = '0.7.0'.freeze
5
5
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elastic-apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikkel Malmberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-23 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: activesupport
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 3.0.0
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: 3.0.0
11
+ date: 2018-05-16 00:00:00.000000000 Z
12
+ dependencies: []
27
13
  description:
28
14
  email:
29
15
  - mikkel@elastic.co
@@ -71,18 +57,6 @@ files:
71
57
  - lib/elastic_apm/filters/request_body_filter.rb
72
58
  - lib/elastic_apm/filters/secrets_filter.rb
73
59
  - lib/elastic_apm/http.rb
74
- - lib/elastic_apm/injectors.rb
75
- - lib/elastic_apm/injectors/action_dispatch.rb
76
- - lib/elastic_apm/injectors/delayed_job.rb
77
- - lib/elastic_apm/injectors/elasticsearch.rb
78
- - lib/elastic_apm/injectors/json.rb
79
- - lib/elastic_apm/injectors/mongo.rb
80
- - lib/elastic_apm/injectors/net_http.rb
81
- - lib/elastic_apm/injectors/redis.rb
82
- - lib/elastic_apm/injectors/sequel.rb
83
- - lib/elastic_apm/injectors/sidekiq.rb
84
- - lib/elastic_apm/injectors/sinatra.rb
85
- - lib/elastic_apm/injectors/tilt.rb
86
60
  - lib/elastic_apm/instrumenter.rb
87
61
  - lib/elastic_apm/internal_error.rb
88
62
  - lib/elastic_apm/log.rb
@@ -102,10 +76,22 @@ files:
102
76
  - lib/elastic_apm/span.rb
103
77
  - lib/elastic_apm/span/context.rb
104
78
  - lib/elastic_apm/span_helpers.rb
79
+ - lib/elastic_apm/spies.rb
80
+ - lib/elastic_apm/spies/action_dispatch.rb
81
+ - lib/elastic_apm/spies/delayed_job.rb
82
+ - lib/elastic_apm/spies/elasticsearch.rb
83
+ - lib/elastic_apm/spies/json.rb
84
+ - lib/elastic_apm/spies/mongo.rb
85
+ - lib/elastic_apm/spies/net_http.rb
86
+ - lib/elastic_apm/spies/redis.rb
87
+ - lib/elastic_apm/spies/sequel.rb
88
+ - lib/elastic_apm/spies/sidekiq.rb
89
+ - lib/elastic_apm/spies/sinatra.rb
90
+ - lib/elastic_apm/spies/tilt.rb
105
91
  - lib/elastic_apm/sql_summarizer.rb
106
92
  - lib/elastic_apm/stacktrace.rb
107
93
  - lib/elastic_apm/stacktrace/frame.rb
108
- - lib/elastic_apm/stacktrace/line_cache.rb
94
+ - lib/elastic_apm/stacktrace_builder.rb
109
95
  - lib/elastic_apm/subscriber.rb
110
96
  - lib/elastic_apm/system_info.rb
111
97
  - lib/elastic_apm/timed_worker.rb
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'elastic_apm/util/lru_cache'
4
-
5
- module ElasticAPM
6
- class Stacktrace
7
- # A basic LRU Cache
8
- # @api private
9
- class LineCache
10
- class << self
11
- def cache
12
- @cache ||= Util::LruCache.new
13
- end
14
-
15
- def get(*key)
16
- cache[key]
17
- end
18
-
19
- def set(*key, value)
20
- cache[key] = value
21
- end
22
- end
23
- end
24
- end
25
- end