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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.rubocop.yml +47 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +47 -0
- data/Gemfile +38 -0
- data/LICENSE +201 -0
- data/README.md +55 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/bin/with_framework +7 -0
- data/elastic-apm.gemspec +22 -0
- data/lib/elastic-apm.rb +4 -0
- data/lib/elastic_apm.rb +92 -0
- data/lib/elastic_apm/agent.rb +164 -0
- data/lib/elastic_apm/config.rb +124 -0
- data/lib/elastic_apm/error.rb +21 -0
- data/lib/elastic_apm/error/context.rb +119 -0
- data/lib/elastic_apm/error/exception.rb +37 -0
- data/lib/elastic_apm/error/log.rb +24 -0
- data/lib/elastic_apm/error_builder.rb +40 -0
- data/lib/elastic_apm/http.rb +103 -0
- data/lib/elastic_apm/injectors.rb +71 -0
- data/lib/elastic_apm/injectors/action_dispatch.rb +26 -0
- data/lib/elastic_apm/injectors/json.rb +22 -0
- data/lib/elastic_apm/injectors/net_http.rb +50 -0
- data/lib/elastic_apm/injectors/redis.rb +33 -0
- data/lib/elastic_apm/injectors/sequel.rb +45 -0
- data/lib/elastic_apm/injectors/sinatra.rb +41 -0
- data/lib/elastic_apm/injectors/tilt.rb +27 -0
- data/lib/elastic_apm/instrumenter.rb +112 -0
- data/lib/elastic_apm/internal_error.rb +5 -0
- data/lib/elastic_apm/log.rb +47 -0
- data/lib/elastic_apm/middleware.rb +30 -0
- data/lib/elastic_apm/normalizers.rb +63 -0
- data/lib/elastic_apm/normalizers/action_controller.rb +24 -0
- data/lib/elastic_apm/normalizers/action_view.rb +72 -0
- data/lib/elastic_apm/normalizers/active_record.rb +41 -0
- data/lib/elastic_apm/railtie.rb +43 -0
- data/lib/elastic_apm/serializers.rb +26 -0
- data/lib/elastic_apm/serializers/errors.rb +40 -0
- data/lib/elastic_apm/serializers/transactions.rb +36 -0
- data/lib/elastic_apm/service_info.rb +66 -0
- data/lib/elastic_apm/span.rb +51 -0
- data/lib/elastic_apm/span/context.rb +20 -0
- data/lib/elastic_apm/span_helpers.rb +37 -0
- data/lib/elastic_apm/sql_summarizer.rb +26 -0
- data/lib/elastic_apm/stacktrace.rb +84 -0
- data/lib/elastic_apm/stacktrace/frame.rb +62 -0
- data/lib/elastic_apm/subscriber.rb +72 -0
- data/lib/elastic_apm/system_info.rb +30 -0
- data/lib/elastic_apm/transaction.rb +92 -0
- data/lib/elastic_apm/util.rb +20 -0
- data/lib/elastic_apm/util/inspector.rb +61 -0
- data/lib/elastic_apm/version.rb +5 -0
- data/lib/elastic_apm/worker.rb +48 -0
- data/vendor/.gitkeep +0 -0
- 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
|