elastic-apm 0.6.2 → 0.7.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 (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