stackify-ruby-apm 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +76 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +68 -0
- data/README.md +23 -0
- data/Rakefile +6 -0
- data/lib/stackify-ruby-apm.rb +4 -0
- data/lib/stackify/agent.rb +186 -0
- data/lib/stackify/config.rb +221 -0
- data/lib/stackify/context.rb +24 -0
- data/lib/stackify/context/request.rb +12 -0
- data/lib/stackify/context/request/socket.rb +21 -0
- data/lib/stackify/context/request/url.rb +44 -0
- data/lib/stackify/context/response.rb +24 -0
- data/lib/stackify/context_builder.rb +81 -0
- data/lib/stackify/error.rb +24 -0
- data/lib/stackify/error/exception.rb +36 -0
- data/lib/stackify/error/log.rb +25 -0
- data/lib/stackify/error_builder.rb +65 -0
- data/lib/stackify/instrumenter.rb +118 -0
- data/lib/stackify/internal_error.rb +5 -0
- data/lib/stackify/log.rb +51 -0
- data/lib/stackify/logger.rb +10 -0
- data/lib/stackify/middleware.rb +78 -0
- data/lib/stackify/naively_hashable.rb +25 -0
- data/lib/stackify/normalizers.rb +71 -0
- data/lib/stackify/normalizers/action_controller.rb +24 -0
- data/lib/stackify/normalizers/action_mailer.rb +23 -0
- data/lib/stackify/normalizers/action_view.rb +72 -0
- data/lib/stackify/normalizers/active_record.rb +71 -0
- data/lib/stackify/railtie.rb +50 -0
- data/lib/stackify/root_info.rb +58 -0
- data/lib/stackify/serializers.rb +27 -0
- data/lib/stackify/serializers/errors.rb +45 -0
- data/lib/stackify/serializers/transactions.rb +71 -0
- data/lib/stackify/span.rb +71 -0
- data/lib/stackify/span/context.rb +26 -0
- data/lib/stackify/spies.rb +89 -0
- data/lib/stackify/spies/action_dispatch.rb +26 -0
- data/lib/stackify/spies/httpclient.rb +47 -0
- data/lib/stackify/spies/mongo.rb +66 -0
- data/lib/stackify/spies/net_http.rb +47 -0
- data/lib/stackify/spies/sinatra.rb +50 -0
- data/lib/stackify/spies/tilt.rb +28 -0
- data/lib/stackify/stacktrace.rb +19 -0
- data/lib/stackify/stacktrace/frame.rb +50 -0
- data/lib/stackify/stacktrace_builder.rb +101 -0
- data/lib/stackify/subscriber.rb +113 -0
- data/lib/stackify/trace_logger.rb +66 -0
- data/lib/stackify/transaction.rb +123 -0
- data/lib/stackify/util.rb +23 -0
- data/lib/stackify/util/dig.rb +31 -0
- data/lib/stackify/util/inflector.rb +91 -0
- data/lib/stackify/util/inspector.rb +59 -0
- data/lib/stackify/util/lru_cache.rb +49 -0
- data/lib/stackify/version.rb +4 -0
- data/lib/stackify/worker.rb +119 -0
- data/lib/stackify_ruby_apm.rb +130 -0
- data/stackify-ruby-apm.gemspec +30 -0
- metadata +187 -0
data/lib/stackify/log.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StackifyRubyAPM
|
4
|
+
# @api private
|
5
|
+
# This module will format the process logs perform between the Agent and Web App
|
6
|
+
module Log
|
7
|
+
PREFIX = '[StackifyRubyAPM] '.freeze
|
8
|
+
|
9
|
+
def debug(msg, *args, &block)
|
10
|
+
log(:debug, msg, *args, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def info(msg, *args, &block)
|
14
|
+
log(:info, msg, *args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def warn(msg, *args, &block)
|
18
|
+
log(:warn, msg, *args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def error(msg, *args, &block)
|
22
|
+
log(:error, msg, *args, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def fatal(msg, *args, &block)
|
26
|
+
log(:fatal, msg, *args, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def log(lvl, msg, *args)
|
30
|
+
return unless logger
|
31
|
+
|
32
|
+
formatted_msg = prepend_prefix(format(msg.to_s, *args))
|
33
|
+
|
34
|
+
return logger.send(lvl, formatted_msg) unless block_given?
|
35
|
+
|
36
|
+
# TODO: dont evaluate block if level is higher
|
37
|
+
logger.send(lvl, "#{formatted_msg}\n#{yield}")
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def prepend_prefix(str)
|
43
|
+
"#{PREFIX}#{str}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def logger
|
47
|
+
return false unless (config = instance_variable_get(:@config))
|
48
|
+
config.logger
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# This class monkey patches the Rack Middleware method: e.g., call(env)
|
4
|
+
# It also handles the submission of transactions to Agent.
|
5
|
+
#
|
6
|
+
# If Agent is running, build_transaction(env) is called:
|
7
|
+
#
|
8
|
+
# -> BUILDS transaction: /stackify_ruby_apm.rb (#self.transaction)
|
9
|
+
# -> builds a [Context] from a Rack `env`: /stackify_ruby_apm.rb (#self.build_context)
|
10
|
+
# -> context may include information about the request,
|
11
|
+
# response, current user and more
|
12
|
+
#
|
13
|
+
# -> SUBMITS transaction: stackify/transaction.rb (#submit)
|
14
|
+
# -> instrumenter submits transaction
|
15
|
+
# -> worker will run (if != running), responsible for sending transactions to APM
|
16
|
+
# for every 10 secs (based on config.flush_interval)
|
17
|
+
# -> transaction will be stored in queue by Agent
|
18
|
+
# -> worker will constantly execute transaction sending to APM
|
19
|
+
#
|
20
|
+
|
21
|
+
module StackifyRubyAPM
|
22
|
+
|
23
|
+
class Middleware
|
24
|
+
def initialize(app)
|
25
|
+
# puts "@stackify_ruby [Middleware] [lib/middleware.rb] initialize(app)"
|
26
|
+
@app = app
|
27
|
+
end
|
28
|
+
|
29
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
30
|
+
# This is where the requests are received, built into a transaction, and then submitted
|
31
|
+
#
|
32
|
+
def call(env)
|
33
|
+
begin
|
34
|
+
|
35
|
+
#if running? && !path_ignored?(env)
|
36
|
+
if running?
|
37
|
+
transaction = build_transaction(env)
|
38
|
+
end
|
39
|
+
|
40
|
+
resp = @app.call env
|
41
|
+
# puts "@stackify_ruby [Middleware] [lib/middleware.rb] Loads call(env) in middleware module"
|
42
|
+
|
43
|
+
submit_transaction(transaction, *resp) if transaction
|
44
|
+
rescue InternalError
|
45
|
+
raise # Don't report StackifyRubyAPM errors
|
46
|
+
rescue ::Exception => e
|
47
|
+
#puts "@stackify_ruby [Middleware] [lib/middleware.rb] Error Message: #{e.message}"
|
48
|
+
transaction.submit('500', status: 500) if transaction
|
49
|
+
raise
|
50
|
+
ensure
|
51
|
+
transaction.release if transaction
|
52
|
+
end
|
53
|
+
|
54
|
+
resp
|
55
|
+
end
|
56
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
57
|
+
|
58
|
+
def submit_transaction(transaction, status, headers, _body)
|
59
|
+
result = status.to_i
|
60
|
+
transaction.submit(result, status: status, headers: headers)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Start of transaction building with params: name, type, context
|
64
|
+
#
|
65
|
+
def build_transaction(env)
|
66
|
+
# puts "@stackify_ruby [Middleware] [lib/middleware.rb] middleware build transaction env"
|
67
|
+
StackifyRubyAPM.transaction 'Rack', 'request', context: StackifyRubyAPM.build_context(env)
|
68
|
+
end
|
69
|
+
|
70
|
+
def running?
|
71
|
+
StackifyRubyAPM.running?
|
72
|
+
end
|
73
|
+
|
74
|
+
def config
|
75
|
+
StackifyRubyAPM.agent.config
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# This module converts its result to a hash.
|
4
|
+
#
|
5
|
+
|
6
|
+
module StackifyRubyAPM
|
7
|
+
# @api private
|
8
|
+
module NaivelyHashable
|
9
|
+
def naively_hashable?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_h
|
14
|
+
instance_variables.each_with_object({}) do |name, h|
|
15
|
+
|
16
|
+
key = name.to_s.delete('@').to_sym
|
17
|
+
value = instance_variable_get(name)
|
18
|
+
is_hashable =
|
19
|
+
value.respond_to?(:naively_hashable?) && value.naively_hashable?
|
20
|
+
|
21
|
+
h[key] = is_hashable ? value.to_h : value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Collects and registers existing events and classes
|
4
|
+
#
|
5
|
+
|
6
|
+
module StackifyRubyAPM # :nodoc:
|
7
|
+
# @api private
|
8
|
+
module Normalizers
|
9
|
+
# @api privagte
|
10
|
+
class Normalizer
|
11
|
+
def initialize(config)
|
12
|
+
@config = config
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.register(name)
|
16
|
+
Normalizers.register(name, self)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.register(name, klass)
|
21
|
+
@registered ||= {}
|
22
|
+
@registered[name] = klass
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.build(config)
|
26
|
+
# puts '@stackify_ruby [Normalizers] [lib/normalizer.rb] self.build(config)'
|
27
|
+
normalizers = @registered.each_with_object({}) do |(name, klass), built|
|
28
|
+
built[name] = klass.new(config)
|
29
|
+
end
|
30
|
+
Collection.new(normalizers)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @api private
|
34
|
+
class Collection
|
35
|
+
# @api private
|
36
|
+
class SkipNormalizer
|
37
|
+
def initialize; end
|
38
|
+
|
39
|
+
def normalize(*_args)
|
40
|
+
:skip
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(normalizers)
|
45
|
+
@normalizers = normalizers
|
46
|
+
@default = SkipNormalizer.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def for(name)
|
50
|
+
@normalizers.fetch(name, @default)
|
51
|
+
end
|
52
|
+
|
53
|
+
def keys
|
54
|
+
@normalizers.keys
|
55
|
+
end
|
56
|
+
|
57
|
+
def normalize(transaction, name, payload)
|
58
|
+
self.for(name).normalize(transaction, name, payload)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
%w[
|
64
|
+
action_controller
|
65
|
+
action_mailer
|
66
|
+
action_view
|
67
|
+
active_record
|
68
|
+
].each do |lib|
|
69
|
+
require "stackify/normalizers/#{lib}"
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StackifyRubyAPM
|
4
|
+
module Normalizers
|
5
|
+
module ActionController
|
6
|
+
# @api private
|
7
|
+
class ProcessActionNormalizer < Normalizer
|
8
|
+
register 'process_action.action_controller'
|
9
|
+
TYPE = 'app.controller.action'.freeze
|
10
|
+
|
11
|
+
def normalize(transaction, _name, payload)
|
12
|
+
transaction.name = endpoint(payload)
|
13
|
+
[transaction.name, TYPE, nil]
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def endpoint(payload)
|
19
|
+
"#{payload[:controller]}##{payload[:action]}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StackifyRubyAPM
|
4
|
+
module Normalizers
|
5
|
+
module ActionMailer
|
6
|
+
# @api private
|
7
|
+
class ProcessActionNormalizer < Normalizer
|
8
|
+
register 'process.action_mailer'
|
9
|
+
TYPE = 'app.mailer.action'.freeze
|
10
|
+
|
11
|
+
def normalize(_transaction, _name, payload)
|
12
|
+
[endpoint(payload), TYPE, nil]
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def endpoint(payload)
|
18
|
+
"#{payload[:mailer]}##{payload[:action]}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StackifyRubyAPM
|
4
|
+
module Normalizers
|
5
|
+
module ActionView
|
6
|
+
# @api private
|
7
|
+
class RenderNormalizer < Normalizer
|
8
|
+
private
|
9
|
+
|
10
|
+
def normalize_render(payload, type)
|
11
|
+
[path_for(payload[:identifier]), type, nil]
|
12
|
+
end
|
13
|
+
|
14
|
+
def path_for(path)
|
15
|
+
return 'Unknown template' unless path
|
16
|
+
return path unless path.start_with?('/')
|
17
|
+
|
18
|
+
view_path(path) || gem_path(path) || 'Absolute path'
|
19
|
+
end
|
20
|
+
|
21
|
+
def view_path(path)
|
22
|
+
root = @config.view_paths.find { |vp| path.start_with?(vp) }
|
23
|
+
return unless root
|
24
|
+
|
25
|
+
strip_root(root, path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def gem_path(path)
|
29
|
+
root = Gem.path.find { |gp| path.start_with? gp }
|
30
|
+
return unless root
|
31
|
+
|
32
|
+
format '$GEM_PATH/%s', strip_root(root, path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def strip_root(root, path)
|
36
|
+
start = root.length + 1
|
37
|
+
path[start, path.length]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @api private
|
42
|
+
class RenderTemplateNormalizer < RenderNormalizer
|
43
|
+
register 'render_template.action_view'
|
44
|
+
TYPE = 'template.view'.freeze
|
45
|
+
|
46
|
+
def normalize(_transaction, _name, payload)
|
47
|
+
normalize_render(payload, TYPE)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @api private
|
52
|
+
class RenderPartialNormalizer < RenderNormalizer
|
53
|
+
register 'render_partial.action_view'
|
54
|
+
TYPE = 'template.view.partial'.freeze
|
55
|
+
|
56
|
+
def normalize(_transaction, _name, payload)
|
57
|
+
normalize_render(payload, TYPE)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @api private
|
62
|
+
class RenderCollectionNormalizer < RenderNormalizer
|
63
|
+
register 'render_collection.action_view'
|
64
|
+
TYPE = 'template.view.collection'.freeze
|
65
|
+
|
66
|
+
def normalize(_transaction, _name, payload)
|
67
|
+
normalize_render(payload, TYPE)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StackifyRubyAPM
|
4
|
+
module Normalizers
|
5
|
+
module ActiveRecord
|
6
|
+
# @api private
|
7
|
+
class SqlNormalizer < Normalizer
|
8
|
+
include Log
|
9
|
+
register 'sql.active_record'
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
super(*args)
|
13
|
+
|
14
|
+
@type = format('db.%s.sql', lookup_adapter || 'unknown').freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
def normalize(_transaction, _name, payload)
|
18
|
+
return :skip if %w[SCHEMA CACHE].include?(payload[:name])
|
19
|
+
|
20
|
+
statement = query_variables(payload)
|
21
|
+
name = payload[:sql] || payload[:name] || 'Default'
|
22
|
+
context = Span::Context.new(statement)
|
23
|
+
[name, @type, context]
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# return back valid PROVIDER based on driver name passed in
|
29
|
+
def get_profiler(driver)
|
30
|
+
if driver.to_s.empty?
|
31
|
+
"generic"
|
32
|
+
elsif driver.include? "mysql"
|
33
|
+
"mysql"
|
34
|
+
elsif driver.include? "pg"
|
35
|
+
"postgresql"
|
36
|
+
elsif driver.include? "oci8"
|
37
|
+
"oracle"
|
38
|
+
elsif driver.include? "db2"
|
39
|
+
"db2"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def query_variables(payload)
|
44
|
+
debug "@stackify_ruby [SqlNormalizer] [normalizers/active_record.rb] query_variables payload:"
|
45
|
+
debug payload.inspect
|
46
|
+
|
47
|
+
if payload[:type_casted_binds]
|
48
|
+
row_id = payload[:type_casted_binds][0]
|
49
|
+
else
|
50
|
+
row_id = []
|
51
|
+
end
|
52
|
+
|
53
|
+
{
|
54
|
+
CATEGORY: 'Database',
|
55
|
+
SUBCATEGORY: 'Execute',
|
56
|
+
COMPONENT_CATEGORY: 'DB Query',
|
57
|
+
COMPONENT_DETAIL: 'Execute SQL Query',
|
58
|
+
SQL: payload[:sql],
|
59
|
+
PROVIDER: get_profiler(lookup_adapter)
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def lookup_adapter
|
64
|
+
::ActiveRecord::Base.connection.adapter_name.downcase
|
65
|
+
rescue StandardError
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|