elastic-apm 0.1.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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +47 -0
  5. data/.travis.yml +5 -0
  6. data/CODE_OF_CONDUCT.md +47 -0
  7. data/Gemfile +38 -0
  8. data/LICENSE +201 -0
  9. data/README.md +55 -0
  10. data/Rakefile +12 -0
  11. data/bin/console +15 -0
  12. data/bin/setup +8 -0
  13. data/bin/with_framework +7 -0
  14. data/elastic-apm.gemspec +22 -0
  15. data/lib/elastic-apm.rb +4 -0
  16. data/lib/elastic_apm.rb +92 -0
  17. data/lib/elastic_apm/agent.rb +164 -0
  18. data/lib/elastic_apm/config.rb +124 -0
  19. data/lib/elastic_apm/error.rb +21 -0
  20. data/lib/elastic_apm/error/context.rb +119 -0
  21. data/lib/elastic_apm/error/exception.rb +37 -0
  22. data/lib/elastic_apm/error/log.rb +24 -0
  23. data/lib/elastic_apm/error_builder.rb +40 -0
  24. data/lib/elastic_apm/http.rb +103 -0
  25. data/lib/elastic_apm/injectors.rb +71 -0
  26. data/lib/elastic_apm/injectors/action_dispatch.rb +26 -0
  27. data/lib/elastic_apm/injectors/json.rb +22 -0
  28. data/lib/elastic_apm/injectors/net_http.rb +50 -0
  29. data/lib/elastic_apm/injectors/redis.rb +33 -0
  30. data/lib/elastic_apm/injectors/sequel.rb +45 -0
  31. data/lib/elastic_apm/injectors/sinatra.rb +41 -0
  32. data/lib/elastic_apm/injectors/tilt.rb +27 -0
  33. data/lib/elastic_apm/instrumenter.rb +112 -0
  34. data/lib/elastic_apm/internal_error.rb +5 -0
  35. data/lib/elastic_apm/log.rb +47 -0
  36. data/lib/elastic_apm/middleware.rb +30 -0
  37. data/lib/elastic_apm/normalizers.rb +63 -0
  38. data/lib/elastic_apm/normalizers/action_controller.rb +24 -0
  39. data/lib/elastic_apm/normalizers/action_view.rb +72 -0
  40. data/lib/elastic_apm/normalizers/active_record.rb +41 -0
  41. data/lib/elastic_apm/railtie.rb +43 -0
  42. data/lib/elastic_apm/serializers.rb +26 -0
  43. data/lib/elastic_apm/serializers/errors.rb +40 -0
  44. data/lib/elastic_apm/serializers/transactions.rb +36 -0
  45. data/lib/elastic_apm/service_info.rb +66 -0
  46. data/lib/elastic_apm/span.rb +51 -0
  47. data/lib/elastic_apm/span/context.rb +20 -0
  48. data/lib/elastic_apm/span_helpers.rb +37 -0
  49. data/lib/elastic_apm/sql_summarizer.rb +26 -0
  50. data/lib/elastic_apm/stacktrace.rb +84 -0
  51. data/lib/elastic_apm/stacktrace/frame.rb +62 -0
  52. data/lib/elastic_apm/subscriber.rb +72 -0
  53. data/lib/elastic_apm/system_info.rb +30 -0
  54. data/lib/elastic_apm/transaction.rb +92 -0
  55. data/lib/elastic_apm/util.rb +20 -0
  56. data/lib/elastic_apm/util/inspector.rb +61 -0
  57. data/lib/elastic_apm/version.rb +5 -0
  58. data/lib/elastic_apm/worker.rb +48 -0
  59. data/vendor/.gitkeep +0 -0
  60. metadata +116 -0
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+
5
+ module ElasticAPM
6
+ # @api private
7
+ module Injectors
8
+ # @api private
9
+ class NetHTTPInjector
10
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
11
+ def install
12
+ Net::HTTP.class_eval do
13
+ alias request_without_apm request
14
+
15
+ def request(req, body = nil, &block)
16
+ unless ElasticAPM.current_transaction
17
+ return request_without_apm(req, body, &block)
18
+ end
19
+
20
+ host, port = req['host'] && req['host'].split(':')
21
+ method = req.method
22
+ path = req.path
23
+ scheme = use_ssl? ? 'https' : 'http'
24
+
25
+ # inside a session
26
+ host ||= address
27
+ port ||= 80
28
+
29
+ # TODO: investigate
30
+ _extra = {
31
+ scheme: scheme,
32
+ port: port,
33
+ path: path
34
+ }
35
+
36
+ name = "#{method} #{host}"
37
+ type = "ext.net_http.#{method}"
38
+
39
+ ElasticAPM.span name, type do
40
+ request_without_apm(req, body, &block)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
46
+ end
47
+
48
+ register 'Net::HTTP', 'net/http', NetHTTPInjector.new
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ module Injectors
6
+ # @api private
7
+ class RedisInjector
8
+ # rubocop:disable Metrics/MethodLength
9
+ def install
10
+ ::Redis::Client.class_eval do
11
+ alias call_without_apm call
12
+
13
+ def call(command, &block)
14
+ name = command[0].upcase
15
+ statement =
16
+ format('%s %s', name, command[1..command.length].join(' '))
17
+ context = Span::Context.new(
18
+ statement: statement,
19
+ type: 'redis'
20
+ )
21
+
22
+ ElasticAPM.span(name.to_s, 'db.redis', context: context) do
23
+ call_without_apm(command, &block)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ # rubocop:enable Metrics/MethodLength
29
+ end
30
+
31
+ register 'Redis', 'redis', RedisInjector.new
32
+ end
33
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elastic_apm/sql_summarizer'
4
+
5
+ module ElasticAPM
6
+ # @api private
7
+ module Injectors
8
+ # @api private
9
+ class SequelInjector
10
+ TYPE = 'db.sequel.sql'.freeze
11
+
12
+ def self.summarizer
13
+ @summarizer ||= SqlSummarizer.new
14
+ end
15
+
16
+ # rubocop:disable Metrics/MethodLength
17
+ def install
18
+ require 'sequel/database/logging'
19
+
20
+ ::Sequel::Database.class_eval do
21
+ alias log_connection_yield_without_apm log_connection_yield
22
+
23
+ def log_connection_yield(sql, *args, &block)
24
+ unless ElasticAPM.current_transaction
25
+ return log_connection_yield_without_apm(sql, *args, &block)
26
+ end
27
+
28
+ summarizer = ElasticAPM::Injectors::SequelInjector.summarizer
29
+ name = summarizer.summarize sql
30
+ context = Span::Context.new(
31
+ statement: sql,
32
+ type: 'sql',
33
+ user: opts[:user]
34
+ )
35
+
36
+ ElasticAPM.span(name, TYPE, context: context, &block)
37
+ end
38
+ end
39
+ end
40
+ # rubocop:enable Metrics/MethodLength
41
+ end
42
+
43
+ register 'Sequel', 'sequel', SequelInjector.new
44
+ end
45
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ module Injectors
6
+ # @api private
7
+ class SinatraInjector
8
+ # rubocop:disable Metrics/MethodLength
9
+ def install
10
+ ::Sinatra::Base.class_eval do
11
+ alias dispatch_without_apm! dispatch!
12
+ alias compile_template_without_apm compile_template
13
+
14
+ def dispatch!(*args, &block)
15
+ dispatch_without_apm!(*args, &block).tap do
16
+ next unless (transaction = ElasticAPM.current_transaction)
17
+ next unless (route = env['sinatra.route'])
18
+
19
+ transaction.name = route
20
+ end
21
+ end
22
+
23
+ def compile_template(engine, data, opts, *args, &block)
24
+ opts[:__elastic_apm_template_name] =
25
+ case data
26
+ when Symbol then data.to_s
27
+ else format('Inline %s', engine)
28
+ end
29
+
30
+ compile_template_without_apm(engine, data, opts, *args, &block)
31
+ end
32
+ end
33
+ end
34
+ # rubocop:enable Metrics/MethodLength
35
+ end
36
+
37
+ register 'Sinatra::Base', 'sinatra/base', SinatraInjector.new
38
+
39
+ require 'elastic_apm/injectors/tilt'
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ module Injectors
6
+ # @api private
7
+ class TiltInjector
8
+ TYPE = 'template.tilt'.freeze
9
+
10
+ def install
11
+ ::Tilt::Template.class_eval do
12
+ alias render_without_apm render
13
+
14
+ def render(*args, &block)
15
+ name = options[:__elastic_apm_template_name] || 'Unknown template'
16
+
17
+ ElasticAPM.span name, TYPE do
18
+ render_without_apm(*args, &block)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ register 'Tilt::Template', 'tilt/template', TiltInjector.new
26
+ end
27
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elastic_apm/subscriber'
4
+ require 'elastic_apm/span'
5
+ require 'elastic_apm/transaction'
6
+
7
+ module ElasticAPM
8
+ # @api private
9
+ class Instrumenter
10
+ include Log
11
+
12
+ KEY = :__elastic_transaction_key
13
+
14
+ # @api private
15
+ class TransactionInfo
16
+ def current
17
+ Thread.current[KEY]
18
+ end
19
+
20
+ def current=(transaction)
21
+ Thread.current[KEY] = transaction
22
+ end
23
+ end
24
+
25
+ def initialize(config, agent, subscriber_class: Subscriber)
26
+ @config = config
27
+ @agent = agent
28
+
29
+ @transaction_info = TransactionInfo.new
30
+
31
+ @subscriber = subscriber_class.new(self)
32
+
33
+ @pending_transactions = []
34
+ @last_sent_transactions = Time.now.utc
35
+ end
36
+
37
+ attr_reader :config, :pending_transactions
38
+
39
+ def start
40
+ @subscriber.register!
41
+ end
42
+
43
+ def stop
44
+ current_transaction.release if current_transaction
45
+ @subscriber.unregister!
46
+ end
47
+
48
+ def current_transaction
49
+ @transaction_info.current
50
+ end
51
+
52
+ def current_transaction=(transaction)
53
+ @transaction_info.current = transaction
54
+ end
55
+
56
+ # rubocop:disable Metrics/MethodLength
57
+ def transaction(*args)
58
+ if (transaction = current_transaction)
59
+ yield transaction if block_given?
60
+ return transaction
61
+ end
62
+
63
+ transaction = Transaction.new self, *args
64
+
65
+ self.current_transaction = transaction
66
+ return transaction unless block_given?
67
+
68
+ begin
69
+ yield transaction
70
+ ensure
71
+ self.current_transaction = nil
72
+ transaction.done
73
+ end
74
+
75
+ transaction
76
+ end
77
+ # rubocop:enable Metrics/MethodLength
78
+
79
+ def span(*args, &block)
80
+ transaction.span(*args, &block)
81
+ end
82
+
83
+ def submit_transaction(transaction)
84
+ @pending_transactions << transaction
85
+
86
+ if config.debug_transactions
87
+ debug('Submitted transaction:') { Util.inspect_transaction transaction }
88
+ end
89
+
90
+ return unless should_flush_transactions?
91
+ flush_transactions
92
+ end
93
+
94
+ def should_flush_transactions?
95
+ return true unless (interval = config.transaction_send_interval)
96
+ Time.now.utc - @last_sent_transactions >= interval
97
+ end
98
+
99
+ def flush_transactions
100
+ return if @pending_transactions.empty?
101
+
102
+ debug 'Flushing transactions'
103
+
104
+ @agent.enqueue_transactions @pending_transactions
105
+
106
+ @last_sent_transactions = Time.now.utc
107
+ @pending_transactions = []
108
+
109
+ true
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ class InternalError < StandardError; end
5
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ module Log
6
+ PREFIX = '** [ElasticAPM] '.freeze
7
+
8
+ def debug(msg, *args, &block)
9
+ log(:debug, msg, *args, &block)
10
+ end
11
+
12
+ def info(msg, *args, &block)
13
+ log(:info, msg, *args, &block)
14
+ end
15
+
16
+ def warn(msg, *args, &block)
17
+ log(:warn, msg, *args, &block)
18
+ end
19
+
20
+ def error(msg, *args, &block)
21
+ log(:error, msg, *args, &block)
22
+ end
23
+
24
+ def fatal(msg, *args, &block)
25
+ log(:fatal, msg, *args, &block)
26
+ end
27
+
28
+ def log(lvl, msg, *args)
29
+ formatted_msg = prepend_prefix(format(msg.to_s, *args))
30
+
31
+ return config.logger.send(lvl, formatted_msg) unless block_given?
32
+
33
+ # TODO: dont evaluate block if level is higher
34
+ config.logger.send(lvl, "#{formatted_msg}\n#{yield}")
35
+ end
36
+
37
+ private
38
+
39
+ def prepend_prefix(str)
40
+ "#{PREFIX}#{str}"
41
+ end
42
+
43
+ def has_logger?
44
+ respond_to?(:config) && config.logger
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ class Middleware
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ # rubocop:disable Metrics/MethodLength
11
+ def call(env)
12
+ begin
13
+ transaction = ElasticAPM.transaction 'Rack', 'request'
14
+ resp = @app.call env
15
+ transaction.submit(resp[0]) if transaction
16
+ rescue InternalError
17
+ raise # Don't report ElasticAPM errors
18
+ rescue ::Exception => e
19
+ ElasticAPM.report(e, rack_env: env, handled: false)
20
+ transaction.submit(500) if transaction
21
+ raise
22
+ ensure
23
+ transaction.release if transaction
24
+ end
25
+
26
+ resp
27
+ end
28
+ # rubocop:enable Metrics/MethodLength
29
+ end
30
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM # :nodoc:
4
+ # @api private
5
+ module Normalizers
6
+ # @api privagte
7
+ class Normalizer
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ def self.register(name)
13
+ Normalizers.register(name, self)
14
+ end
15
+ end
16
+
17
+ def self.register(name, klass)
18
+ @registered ||= {}
19
+ @registered[name] = klass
20
+ end
21
+
22
+ def self.build(config)
23
+ normalizers = @registered.each_with_object({}) do |(name, klass), built|
24
+ built[name] = klass.new(config)
25
+ end
26
+
27
+ Collection.new(normalizers)
28
+ end
29
+
30
+ # @api private
31
+ class Collection
32
+ # @api private
33
+ class SkipNormalizer
34
+ def initialize; end
35
+
36
+ def normalize(*_args)
37
+ :skip
38
+ end
39
+ end
40
+
41
+ def initialize(normalizers)
42
+ @normalizers = normalizers
43
+ @default = SkipNormalizer.new
44
+ end
45
+
46
+ def for(name)
47
+ @normalizers.fetch(name, @default)
48
+ end
49
+
50
+ def keys
51
+ @normalizers.keys
52
+ end
53
+
54
+ def normalize(transaction, name, payload)
55
+ self.for(name).normalize(transaction, name, payload)
56
+ end
57
+ end
58
+ end
59
+
60
+ %w[action_controller action_view active_record].each do |lib|
61
+ require "elastic_apm/normalizers/#{lib}"
62
+ end
63
+ end