stackify-ruby-apm 0.9.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.
- 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
|