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
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# This class initializes the parameters and variables for the context of Transaction/Span
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'stackify/context/request'
|
7
|
+
require 'stackify/context/request/socket'
|
8
|
+
require 'stackify/context/request/url'
|
9
|
+
require 'stackify/context/response'
|
10
|
+
|
11
|
+
module StackifyRubyAPM
|
12
|
+
# @api private
|
13
|
+
class Context
|
14
|
+
include NaivelyHashable
|
15
|
+
|
16
|
+
attr_accessor :request, :response
|
17
|
+
attr_reader :custom, :tags
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@custom = {}
|
21
|
+
@tags = {}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StackifyRubyAPM
|
4
|
+
# @api private
|
5
|
+
class Context
|
6
|
+
# @api private
|
7
|
+
class Request
|
8
|
+
# @api private
|
9
|
+
class Socket
|
10
|
+
include NaivelyHashable
|
11
|
+
|
12
|
+
def initialize(req)
|
13
|
+
@remote_addr = req.ip
|
14
|
+
@encrypted = req.scheme == 'https'
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :remote_addr, :encrypted
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StackifyRubyAPM
|
4
|
+
# @api private
|
5
|
+
class Context
|
6
|
+
# @api private
|
7
|
+
class Request
|
8
|
+
# @api private
|
9
|
+
class Url
|
10
|
+
include NaivelyHashable
|
11
|
+
|
12
|
+
SKIPPED_PORTS = {
|
13
|
+
'http' => 80,
|
14
|
+
'https' => 443
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def initialize(req)
|
18
|
+
@protocol = req.scheme
|
19
|
+
@hostname = req.host
|
20
|
+
@port = req.port.to_s
|
21
|
+
@pathname = req.path
|
22
|
+
@search = req.query_string
|
23
|
+
@hash = nil
|
24
|
+
@full = build_full_url req
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :protocol, :hostname, :port, :pathname, :search, :hash,
|
28
|
+
:full
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def build_full_url(req)
|
33
|
+
url = "#{req.scheme}://#{req.host}"
|
34
|
+
|
35
|
+
if req.port != SKIPPED_PORTS[req.scheme]
|
36
|
+
url += ":#{req.port}"
|
37
|
+
end
|
38
|
+
|
39
|
+
url + req.fullpath
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StackifyRubyAPM
|
4
|
+
class Context
|
5
|
+
# @api private
|
6
|
+
class Response
|
7
|
+
include NaivelyHashable
|
8
|
+
|
9
|
+
def initialize(
|
10
|
+
status_code,
|
11
|
+
headers: {},
|
12
|
+
headers_sent: true,
|
13
|
+
finished: true
|
14
|
+
)
|
15
|
+
@status_code = status_code
|
16
|
+
@headers = headers
|
17
|
+
@headers_sent = headers_sent
|
18
|
+
@finished = finished
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_accessor :status_code, :headers, :headers_sent, :finished
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# This class builds the context of the Transaction/Span
|
4
|
+
# Context represents the HTTP request information such as headers, cookies, body.
|
5
|
+
#
|
6
|
+
|
7
|
+
module StackifyRubyAPM
|
8
|
+
# @api private
|
9
|
+
class ContextBuilder
|
10
|
+
def initialize(_agent); end
|
11
|
+
|
12
|
+
# Creates context
|
13
|
+
#
|
14
|
+
def build(rack_env)
|
15
|
+
# puts "@stackify_ruby [lib/context_builder.rb] Loading ContextBuilder build(rack_env)"
|
16
|
+
context = Context.new
|
17
|
+
apply_to_request(context, rack_env)
|
18
|
+
context
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# rubocop:disable Metrics/AbcSize
|
24
|
+
#
|
25
|
+
# Request format and values are assigned to context
|
26
|
+
#
|
27
|
+
def apply_to_request(context, rack_env)
|
28
|
+
# puts "@stackify_ruby [lib/context_builder.rb] Loading ContextBuilder apply_to_request(context, rack_env)"
|
29
|
+
req = rails_req?(rack_env) ? rack_env : Rack::Request.new(rack_env)
|
30
|
+
context.request = Context::Request.new unless context.request
|
31
|
+
request = context.request
|
32
|
+
request.socket = Context::Request::Socket.new(req).to_h
|
33
|
+
request.http_version = build_http_version rack_env
|
34
|
+
request.method = req.request_method
|
35
|
+
request.url = Context::Request::Url.new(req).to_h
|
36
|
+
request.headers, request.env = get_headers_and_env(rack_env)
|
37
|
+
request.body = get_body(req)
|
38
|
+
|
39
|
+
context
|
40
|
+
end
|
41
|
+
# rubocop:enable Metrics/AbcSize
|
42
|
+
|
43
|
+
def get_body(req)
|
44
|
+
return req.POST if req.form_data?
|
45
|
+
|
46
|
+
body = req.body.read
|
47
|
+
req.body.rewind
|
48
|
+
body
|
49
|
+
end
|
50
|
+
|
51
|
+
def rails_req?(env)
|
52
|
+
defined?(ActionDispatch::Request) &&
|
53
|
+
env.is_a?(ActionDispatch::Request)
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_headers_and_env(rack_env)
|
57
|
+
# In Rails < 5 ActionDispatch::Request inherits from Hash
|
58
|
+
headers =
|
59
|
+
rack_env.respond_to?(:headers) ? rack_env.headers : rack_env
|
60
|
+
|
61
|
+
headers.each_with_object([{}, {}]) do |(key, value), (http, env)|
|
62
|
+
next unless key == key.upcase
|
63
|
+
|
64
|
+
if key.start_with?('HTTP_')
|
65
|
+
http[camel_key(key)] = value
|
66
|
+
else
|
67
|
+
env[key] = value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def camel_key(key)
|
73
|
+
key.gsub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
|
74
|
+
end
|
75
|
+
|
76
|
+
def build_http_version(rack_env)
|
77
|
+
return unless (http_version = rack_env['HTTP_VERSION'])
|
78
|
+
http_version.gsub(%r{HTTP/}, '')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stackify/stacktrace'
|
4
|
+
require 'stackify/context'
|
5
|
+
require 'stackify/error/exception'
|
6
|
+
require 'stackify/error/log'
|
7
|
+
|
8
|
+
module StackifyRubyAPM
|
9
|
+
# @api private
|
10
|
+
class Error
|
11
|
+
def initialize(culprit: nil)
|
12
|
+
@id = SecureRandom.uuid
|
13
|
+
@culprit = culprit
|
14
|
+
|
15
|
+
@timestamp = Util.micros
|
16
|
+
@context = Context.new
|
17
|
+
|
18
|
+
@transaction_id = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_accessor :id, :culprit, :exception, :log, :transaction_id, :context
|
22
|
+
attr_reader :timestamp
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
module StackifyRubyAPM
|
3
|
+
class Error
|
4
|
+
# @api private
|
5
|
+
class Exception
|
6
|
+
MOD_SPLIT = '::'.freeze
|
7
|
+
|
8
|
+
def initialize(exception, **attrs)
|
9
|
+
@message =
|
10
|
+
"#{exception.class}: #{exception.message}"
|
11
|
+
@type = exception.class.to_s
|
12
|
+
@module = format_module exception
|
13
|
+
|
14
|
+
attrs.each do |key, val|
|
15
|
+
send(:"#{key}=", val)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor(
|
20
|
+
:attributes,
|
21
|
+
:code,
|
22
|
+
:handled,
|
23
|
+
:message,
|
24
|
+
:module,
|
25
|
+
:stacktrace,
|
26
|
+
:type
|
27
|
+
)
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def format_module(exception)
|
32
|
+
exception.class.to_s.split(MOD_SPLIT)[0...-1].join(MOD_SPLIT)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StackifyRubyAPM
|
4
|
+
class Error
|
5
|
+
# @api private
|
6
|
+
class Log
|
7
|
+
def initialize(message, attrs = {})
|
8
|
+
@message = message
|
9
|
+
|
10
|
+
attrs.each do |key, val|
|
11
|
+
send(:"#{key}=", val)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor(
|
16
|
+
:level,
|
17
|
+
:logger_name,
|
18
|
+
:message,
|
19
|
+
:param_message,
|
20
|
+
:stacktrace
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# This module build errors/exceptions and the Frames/stacktraces affected/related to the error
|
4
|
+
#
|
5
|
+
module StackifyRubyAPM
|
6
|
+
# @api private
|
7
|
+
class ErrorBuilder
|
8
|
+
def initialize(agent)
|
9
|
+
@agent = agent
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_exception(exception, handled: true)
|
13
|
+
error = Error.new
|
14
|
+
error.exception = Error::Exception.new(exception, handled: handled)
|
15
|
+
|
16
|
+
if exception.backtrace
|
17
|
+
add_stacktrace error, :exception, exception.backtrace
|
18
|
+
end
|
19
|
+
|
20
|
+
add_transaction_id error
|
21
|
+
|
22
|
+
if (transaction = StackifyRubyAPM.current_transaction)
|
23
|
+
error.context = transaction.context.dup
|
24
|
+
end
|
25
|
+
|
26
|
+
error
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_log(message, backtrace: nil, **attrs)
|
30
|
+
error = Error.new
|
31
|
+
error.log = Error::Log.new(message, **attrs)
|
32
|
+
|
33
|
+
if backtrace
|
34
|
+
add_stacktrace error, :log, backtrace
|
35
|
+
end
|
36
|
+
|
37
|
+
add_transaction_id error
|
38
|
+
|
39
|
+
error
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def add_stacktrace(error, kind, backtrace)
|
45
|
+
stacktrace =
|
46
|
+
@agent.stacktrace_builder.build(backtrace, type: :error)
|
47
|
+
return unless stacktrace
|
48
|
+
|
49
|
+
case kind
|
50
|
+
when :exception
|
51
|
+
error.exception.stacktrace = stacktrace
|
52
|
+
when :log
|
53
|
+
error.log.stacktrace = stacktrace
|
54
|
+
end
|
55
|
+
|
56
|
+
error.culprit = stacktrace.frames.first.function
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_transaction_id(error)
|
60
|
+
return unless (transaction = StackifyRubyAPM.current_transaction)
|
61
|
+
error.transaction_id = transaction.id
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
@@ -0,0 +1,118 @@
|
|
1
|
+
#
|
2
|
+
# The Instrumenter will build/process the transactions and spans.
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'stackify/span'
|
6
|
+
require 'stackify/transaction'
|
7
|
+
|
8
|
+
module StackifyRubyAPM
|
9
|
+
# @api private
|
10
|
+
class Instrumenter
|
11
|
+
include Log
|
12
|
+
|
13
|
+
KEY = :_stackify_transaction_key
|
14
|
+
|
15
|
+
class TransactionInfo
|
16
|
+
def initialize
|
17
|
+
self.current = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def current
|
21
|
+
# puts '@stackify_ruby [Instrumenter] [lib/instrumenter.rb] TransactionInfo.current()'
|
22
|
+
Thread.current[KEY]
|
23
|
+
end
|
24
|
+
|
25
|
+
def current=(transaction)
|
26
|
+
Thread.current[KEY] = transaction
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(agent)
|
31
|
+
debug '@stackify_ruby [Instrumenter] [lib/instrumenter.rb] initialize()'
|
32
|
+
@agent = agent
|
33
|
+
@config = agent.config
|
34
|
+
|
35
|
+
@transaction_info = TransactionInfo.new
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :agent, :config, :pending_transactions
|
39
|
+
|
40
|
+
def stop
|
41
|
+
current_transaction.release if current_transaction
|
42
|
+
@subscriber.unregister! if @subscriber
|
43
|
+
end
|
44
|
+
|
45
|
+
def subscriber=(subscriber)
|
46
|
+
@subscriber = subscriber
|
47
|
+
@subscriber.register!
|
48
|
+
end
|
49
|
+
|
50
|
+
def current_transaction
|
51
|
+
@transaction_info.current
|
52
|
+
end
|
53
|
+
|
54
|
+
def current_transaction=(transaction)
|
55
|
+
@transaction_info.current = transaction
|
56
|
+
end
|
57
|
+
|
58
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
59
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
60
|
+
#
|
61
|
+
# Creates a new transaction or return the currently running
|
62
|
+
#
|
63
|
+
def transaction(*args)
|
64
|
+
debug "Instrumenter function transaction(*args)"
|
65
|
+
unless config.instrument
|
66
|
+
yield if block_given?
|
67
|
+
return
|
68
|
+
end
|
69
|
+
|
70
|
+
if (transaction = current_transaction)
|
71
|
+
yield transaction if block_given?
|
72
|
+
return transaction
|
73
|
+
end
|
74
|
+
|
75
|
+
transaction = Transaction.new self, *args
|
76
|
+
|
77
|
+
self.current_transaction = transaction
|
78
|
+
return transaction unless block_given?
|
79
|
+
|
80
|
+
begin
|
81
|
+
yield transaction
|
82
|
+
ensure
|
83
|
+
self.current_transaction = nil
|
84
|
+
transaction.done
|
85
|
+
end
|
86
|
+
|
87
|
+
#puts transaction.inspect
|
88
|
+
|
89
|
+
transaction
|
90
|
+
end
|
91
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
92
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
93
|
+
|
94
|
+
def span(*args, &block)
|
95
|
+
unless current_transaction
|
96
|
+
return yield if block_given?
|
97
|
+
return
|
98
|
+
end
|
99
|
+
|
100
|
+
current_transaction.span(*args, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Once the transaction is submitted it will be stored temporarily in queue
|
104
|
+
#
|
105
|
+
def submit_transaction(transaction)
|
106
|
+
debug '@stackify_ruby [Instrumenter] [lib/instrumenter.rb] submit_transaction(transaction):'
|
107
|
+
debug transaction.inspect
|
108
|
+
agent.enqueue_transaction transaction
|
109
|
+
return unless config.debug_transactions
|
110
|
+
end
|
111
|
+
|
112
|
+
def inspect
|
113
|
+
'<StackifyRubyAPM::Instrumenter ' \
|
114
|
+
"current_transaction=#{current_transaction.inspect}" \
|
115
|
+
'>'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|