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,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ class SqlSummarizer
6
+ REGEXES = {
7
+ /^SELECT .* FROM ([^ ]+)/i => 'SELECT FROM ',
8
+ /^INSERT INTO ([^ ]+)/i => 'INSERT INTO ',
9
+ /^UPDATE ([^ ]+)/i => 'UPDATE ',
10
+ /^DELETE FROM ([^ ]+)/i => 'DELETE FROM '
11
+ }.freeze
12
+
13
+ def self.cache
14
+ @cache ||= {}
15
+ end
16
+
17
+ def summarize(sql)
18
+ self.class.cache[sql] ||=
19
+ REGEXES.find do |regex, sig|
20
+ if (match = sql.match(regex))
21
+ break format("#{sig}#{match[1]}")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elastic_apm/stacktrace/frame'
4
+
5
+ module ElasticAPM
6
+ # @api private
7
+ class Stacktrace
8
+ def initialize(backtrace)
9
+ @backtrace = backtrace
10
+ end
11
+
12
+ attr_reader :frames
13
+
14
+ def self.build(builder, backtrace)
15
+ return nil unless backtrace
16
+
17
+ stack = new(backtrace)
18
+ stack.build_frames(builder)
19
+ stack
20
+ end
21
+
22
+ def build_frames(builder)
23
+ @frames = @backtrace.reverse.map do |line|
24
+ build_frame(builder, line)
25
+ end
26
+ end
27
+
28
+ def length
29
+ frames.length
30
+ end
31
+
32
+ def to_a
33
+ frames.map(&:to_h)
34
+ end
35
+
36
+ private
37
+
38
+ JAVA_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/
39
+ RUBY_FORMAT = /^(.+?):(\d+)(?::in `(.+?)')?$/
40
+
41
+ def parse_line(line)
42
+ ruby_match = line.match(RUBY_FORMAT)
43
+
44
+ if ruby_match
45
+ _, file, number, method = ruby_match.to_a
46
+ file.sub!(/\.class$/, '.rb')
47
+ module_name = nil
48
+ else
49
+ java_match = line.match(JAVA_FORMAT)
50
+ _, module_name, method, file, number = java_match.to_a
51
+ end
52
+
53
+ [file, number, method, module_name]
54
+ end
55
+
56
+ def build_frame(_builder, line)
57
+ abs_path, lineno, function, _module_name = parse_line(line)
58
+
59
+ frame = Frame.new
60
+ frame.abs_path = abs_path
61
+ frame.filename = strip_load_path(abs_path)
62
+ frame.function = function
63
+ frame.lineno = lineno.to_i
64
+ frame.build_context 3
65
+
66
+ frame
67
+ end
68
+
69
+ def strip_load_path(path)
70
+ return nil unless path
71
+
72
+ prefix =
73
+ $LOAD_PATH
74
+ .map(&:to_s)
75
+ .select { |s| path.start_with?(s) }
76
+ .sort_by(&:length)
77
+ .last
78
+
79
+ return path unless prefix
80
+
81
+ path[prefix.chomp(File::SEPARATOR).length + 1..-1]
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ class Stacktrace
5
+ # @api private
6
+ class Frame
7
+ attr_accessor(
8
+ :abs_path,
9
+ :filename,
10
+ :function,
11
+ :vars,
12
+ :pre_context,
13
+ :context_line,
14
+ :post_context,
15
+ :in_app,
16
+ :lineno,
17
+ :module,
18
+ :colno
19
+ )
20
+
21
+ # rubocop:disable Metrics/AbcSize
22
+ def build_context(context_line_count)
23
+ return unless abs_path
24
+
25
+ file_lines = [nil] + read_lines(abs_path)
26
+
27
+ self.context_line = file_lines[lineno]
28
+ self.pre_context =
29
+ file_lines[(lineno - context_line_count - 1)...lineno]
30
+ self.post_context =
31
+ file_lines[(lineno + 1)..(lineno + context_line_count)]
32
+ end
33
+ # rubocop:enable Metrics/AbcSize
34
+
35
+ # rubocop:disable Metrics/MethodLength
36
+ def to_h
37
+ {
38
+ abs_path: abs_path,
39
+ filename: filename,
40
+ function: function,
41
+ vars: vars,
42
+ pre_context: pre_context,
43
+ context_line: context_line,
44
+ post_context: post_context,
45
+ in_app: in_app,
46
+ lineno: lineno,
47
+ module: self.module,
48
+ coln: colno
49
+ }
50
+ end
51
+ # rubocop:enable Metrics/MethodLength
52
+
53
+ private
54
+
55
+ def read_lines(path)
56
+ File.readlines(path)
57
+ rescue Errno::ENOENT
58
+ []
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/notifications'
4
+ require 'elastic_apm/normalizers'
5
+
6
+ module ElasticAPM
7
+ # @api private
8
+ class Subscriber
9
+ include Log
10
+
11
+ def initialize(agent)
12
+ @agent = agent
13
+ @config = agent.config
14
+ @normalizers = Normalizers.build(config)
15
+ end
16
+
17
+ attr_reader :config
18
+
19
+ def register!
20
+ unregister! if @subscription
21
+
22
+ @subscription =
23
+ ActiveSupport::Notifications.subscribe(notifications_regex, self)
24
+ end
25
+
26
+ def unregister!
27
+ ActiveSupport::Notifications.unsubscribe @subscription
28
+ @subscription = nil
29
+ end
30
+
31
+ # AS::Notifications API
32
+
33
+ Notification = Struct.new(:id, :span)
34
+
35
+ def start(name, id, payload)
36
+ # debug "AS::Notification#start:#{name}:#{id}"
37
+ return unless (transaction = @agent.current_transaction)
38
+
39
+ normalized = @normalizers.normalize(transaction, name, payload)
40
+
41
+ span =
42
+ if normalized == :skip
43
+ nil
44
+ else
45
+ name, type, context = normalized
46
+ transaction.span(name, type, context: context)
47
+ end
48
+
49
+ transaction.notifications << Notification.new(id, span)
50
+ end
51
+
52
+ def finish(_name, id, _payload)
53
+ # debug "AS::Notification#finish:#{name}:#{id}"
54
+ return unless (transaction = @agent.current_transaction)
55
+
56
+ while (notification = transaction.notifications.pop)
57
+ next unless notification.id == id
58
+
59
+ if (span = notification.span)
60
+ span.done
61
+ end
62
+ return
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def notifications_regex
69
+ @notifications_regex ||= /(#{@normalizers.keys.join('|')})/
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ class SystemInfo
6
+ def initialize(config)
7
+ @config = config
8
+ end
9
+
10
+ attr_reader :config
11
+
12
+ def build
13
+ {
14
+ hostname: `hostname`,
15
+ architecture: platform.cpu,
16
+ platform: platform.os
17
+ }
18
+ end
19
+
20
+ def self.build(config)
21
+ new(config).build
22
+ end
23
+
24
+ private
25
+
26
+ def platform
27
+ @platform ||= Gem::Platform.local
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ class Transaction
6
+ def initialize(instrumenter, name, type = 'custom')
7
+ @instrumenter = instrumenter
8
+ @name = name
9
+ @type = type
10
+
11
+ @timestamp = Util.micros
12
+
13
+ @spans = []
14
+ @notifications = []
15
+ @span_id_ticker = -1
16
+
17
+ @notifications = [] # for AS::Notifications
18
+
19
+ yield self if block_given?
20
+ end
21
+
22
+ attr_accessor :name, :result, :type
23
+ attr_reader :duration, :root_span, :timestamp, :spans, :notifications
24
+
25
+ def release
26
+ @instrumenter.current_transaction = nil
27
+ end
28
+
29
+ def done(result = nil)
30
+ @result = result
31
+
32
+ @duration = Util.micros - @timestamp
33
+
34
+ self
35
+ end
36
+
37
+ def done?
38
+ !!(@result && @duration)
39
+ end
40
+
41
+ def submit(result = nil)
42
+ done result
43
+
44
+ release
45
+
46
+ @instrumenter.submit_transaction self
47
+
48
+ self
49
+ end
50
+
51
+ def running_spans
52
+ spans.select(&:running?)
53
+ end
54
+
55
+ def span(name, type = nil, context: nil)
56
+ span = next_span(name, type, context)
57
+ spans << span
58
+ span.start
59
+
60
+ return span unless block_given?
61
+
62
+ begin
63
+ result = yield span
64
+ ensure
65
+ span.done
66
+ end
67
+
68
+ result
69
+ end
70
+
71
+ def current_span
72
+ spans.reverse.lazy.find(&:running?)
73
+ end
74
+
75
+ private
76
+
77
+ def next_span_id
78
+ @span_id_ticker += 1
79
+ end
80
+
81
+ def next_span(name, type, context)
82
+ Span.new(
83
+ self,
84
+ next_span_id,
85
+ name,
86
+ type,
87
+ parent: current_span,
88
+ context: context
89
+ )
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ # @api private
5
+ module Util
6
+ def self.nearest_minute(target = Time.now.utc)
7
+ target - target.to_i % 60
8
+ end
9
+
10
+ def self.micros(target = Time.now.utc)
11
+ target.to_i * 1_000_000 + target.usec
12
+ end
13
+
14
+ def self.inspect_transaction(transaction)
15
+ Inspector.new.transaction transaction
16
+ end
17
+ end
18
+ end
19
+
20
+ require 'elastic_apm/util/inspector'
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ module Util
5
+ # @api private
6
+ class Inspector
7
+ def initialize(width = 80)
8
+ @width = width
9
+ end
10
+
11
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
12
+ def transaction(transaction)
13
+ unless transaction.done?
14
+ raise ArgumentError, 'Transaction still running'
15
+ end
16
+
17
+ width_factor = @width.to_f / ms(transaction.duration)
18
+
19
+ lines = ['=' * @width]
20
+ lines << "[T] #{transaction.name} " \
21
+ "- #{transaction.type} (#{ms transaction.duration} ms)"
22
+ lines << "+#{'-' * (@width - 2)}+"
23
+
24
+ transaction.spans.each do |span|
25
+ indent = (ms(span.relative_start) * width_factor).to_i
26
+
27
+ if span.duration
28
+ span_width = ms(span.duration) * width_factor
29
+ duration_desc = ms(span.duration)
30
+ else
31
+ span_width = width - indent
32
+ duration_desc = 'RUNNING'
33
+ end
34
+
35
+ description = "[#{span.id}] " \
36
+ "#{span.name} - #{span.type} (#{duration_desc} ms)"
37
+ description_indent = [
38
+ 0,
39
+ [indent, @width - description.length].min
40
+ ].max
41
+
42
+ lines << "#{' ' * description_indent}#{description}"
43
+ lines << "#{' ' * indent}+#{'-' * [(span_width - 2), 0].max}+"
44
+ end
45
+
46
+ lines.map { |s| s[0..@width] }.join("\n")
47
+ rescue StandardError => e
48
+ puts e
49
+ puts e.backspan.join("\n")
50
+ nil
51
+ end
52
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
53
+
54
+ private
55
+
56
+ def ms(micros)
57
+ micros.to_f / 1_000
58
+ end
59
+ end
60
+ end
61
+ end