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.
- 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
|