elastic-apm 0.1.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 (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