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,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# This class loads the specified config of the Rails app and starts the Agent.
|
4
|
+
#
|
5
|
+
# APM Config setting is done at stackify/config.rb
|
6
|
+
#
|
7
|
+
# After the Agent has been started:
|
8
|
+
# -> the enabled Spies (libraries) are registered
|
9
|
+
# -> the Subscriber is called to register the events
|
10
|
+
# -> the Middleware is then inserted
|
11
|
+
#
|
12
|
+
|
13
|
+
require 'stackify/subscriber'
|
14
|
+
|
15
|
+
module StackifyRubyAPM
|
16
|
+
# @api private
|
17
|
+
# Railtie - core of the Rails framework and provides several hooks to modify the initialization process.
|
18
|
+
class Railtie < Rails::Railtie
|
19
|
+
|
20
|
+
config.stackify = ActiveSupport::OrderedOptions.new
|
21
|
+
|
22
|
+
Config::DEFAULTS.each { |option, value| config.stackify[option] = value }
|
23
|
+
|
24
|
+
# This will overwrite the default values of the config based from stackify_apm.yml initialized
|
25
|
+
|
26
|
+
initializer 'stackify_apm.initialize' do |app|
|
27
|
+
# puts '@stackify_ruby [Railtie] [lib/railtie.rb] initializer'
|
28
|
+
config = app.config.stackify.merge(app: app).tap do |c|
|
29
|
+
# Prepend Rails.root to log_path if present
|
30
|
+
if c.log_path && !c.log_path.start_with?('/')
|
31
|
+
c.log_path = Rails.root.join(c.log_path)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
begin
|
36
|
+
agent = StackifyRubyAPM.start config
|
37
|
+
if agent
|
38
|
+
agent.instrumenter.subscriber = StackifyRubyAPM::Subscriber.new(agent)
|
39
|
+
app.middleware.insert 0, Middleware # add a middleware to the application
|
40
|
+
end
|
41
|
+
rescue StandardError => e
|
42
|
+
Rails.logger.error "[Railtie] Failed to start: #{e.message}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
config.after_initialize do
|
47
|
+
require 'stackify/spies/action_dispatch'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# This will format the root information of the Transaction.
|
4
|
+
#
|
5
|
+
|
6
|
+
module StackifyRubyAPM
|
7
|
+
# @api private
|
8
|
+
class RootInfo
|
9
|
+
def initialize(config, transaction)
|
10
|
+
@transaction = transaction
|
11
|
+
@config = config
|
12
|
+
end
|
13
|
+
|
14
|
+
# rubocop:disable Metrics/MethodLength
|
15
|
+
# rubocop:disable Style/StringLiterals
|
16
|
+
def build
|
17
|
+
# get process id
|
18
|
+
pid = $PID || Process.pid
|
19
|
+
|
20
|
+
base = {
|
21
|
+
CATEGORY: 'Ruby',
|
22
|
+
APPLICATION_PATH: '/',
|
23
|
+
APPLICATION_FILESYSTEM_PATH: @config.root_path,
|
24
|
+
APPLICATION_NAME: @config.application_name,
|
25
|
+
APPLICATION_ENV: @config.environment_name,
|
26
|
+
REPORTING_URL: @transaction.name,
|
27
|
+
METHOD: @transaction.context.request.method,
|
28
|
+
STATUS: @transaction.context.response.status_code,
|
29
|
+
URL: @transaction.context.request.url[:full],
|
30
|
+
TRACETYPE: 'WEBAPP',
|
31
|
+
TRACE_ID: @transaction.id,
|
32
|
+
THREAD_ID: Thread.current.object_id,
|
33
|
+
TRACE_SOURCE: 'Ruby',
|
34
|
+
TRACE_TARGET: 'RETRACE',
|
35
|
+
HOST_NAME: @config.hostname || `hostname`,
|
36
|
+
OS_TYPE: 'LINUX',
|
37
|
+
PROCESS_ID: pid,
|
38
|
+
TRACE_VERSION: '2.0'
|
39
|
+
}
|
40
|
+
end
|
41
|
+
# rubocop:enable Metrics/MethodLength
|
42
|
+
|
43
|
+
def self.build(config, transaction)
|
44
|
+
new(config, transaction).build
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def runtime
|
50
|
+
case RUBY_ENGINE
|
51
|
+
when 'ruby'
|
52
|
+
{ name: RUBY_ENGINE, version: RUBY_VERSION }
|
53
|
+
when 'jruby'
|
54
|
+
{ name: RUBY_ENGINE, version: ENV['JRUBY_VERSION'] }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'json'
|
3
|
+
# After the spans are being build it will serialize or rebuild or reformat the spans created into json format
|
4
|
+
module StackifyRubyAPM
|
5
|
+
# @api private
|
6
|
+
module Serializers
|
7
|
+
# @api private
|
8
|
+
class Serializer
|
9
|
+
def initialize(config)
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def micros_to_time(micros)
|
16
|
+
Time.at(ms(micros) / 1_000)
|
17
|
+
end
|
18
|
+
|
19
|
+
def ms(micros)
|
20
|
+
micros.to_f / 1_000
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'stackify/serializers/transactions'
|
27
|
+
require 'stackify/serializers/errors'
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# This module gathers the built Errors and Exceptions into hash formats
|
4
|
+
#
|
5
|
+
module StackifyRubyAPM
|
6
|
+
module Serializers
|
7
|
+
# @api private
|
8
|
+
class Errors < Serializer
|
9
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
10
|
+
def build(error)
|
11
|
+
|
12
|
+
if (exception = error.exception)
|
13
|
+
|
14
|
+
base = {
|
15
|
+
CaughtBy: exception.module,
|
16
|
+
Exception: exception.type,
|
17
|
+
Message: exception.message,
|
18
|
+
Timestamp: micros_to_time(error.timestamp).utc.iso8601(3),
|
19
|
+
Frames: exception.stacktrace.to_a,
|
20
|
+
}
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
base
|
25
|
+
end
|
26
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
27
|
+
|
28
|
+
def build_all(errors)
|
29
|
+
{ exceptions: Array(errors).map(&method(:build)) }
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def build_log(log)
|
35
|
+
{
|
36
|
+
message: log.message,
|
37
|
+
level: log.level,
|
38
|
+
logger_name: log.logger_name,
|
39
|
+
param_message: log.param_message,
|
40
|
+
stacktrace: log.stacktrace.to_a
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# This class builds and reformats all the gathered spans within the transaction.
|
4
|
+
#
|
5
|
+
|
6
|
+
module StackifyRubyAPM
|
7
|
+
module Serializers
|
8
|
+
# @api private
|
9
|
+
|
10
|
+
class Transactions < Serializer
|
11
|
+
include Log
|
12
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
13
|
+
def build(config, transaction)
|
14
|
+
|
15
|
+
root_span_json = {
|
16
|
+
id: -1,
|
17
|
+
call: transaction.name,
|
18
|
+
reqBegin: transaction.timestamp,
|
19
|
+
reqEnd: transaction.duration,
|
20
|
+
props: RootInfo.build(config, transaction)
|
21
|
+
}
|
22
|
+
|
23
|
+
# serialize all the spans
|
24
|
+
children_spans_json = []
|
25
|
+
transaction.spans.each do |span|
|
26
|
+
children_spans_json.push(build_span(span))
|
27
|
+
end
|
28
|
+
|
29
|
+
add_children_spans(root_span_json, children_spans_json)
|
30
|
+
|
31
|
+
root_span_json
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Add Children Spans to span_json
|
37
|
+
def add_children_spans(span_json, children_spans_json)
|
38
|
+
id = span_json[:id]
|
39
|
+
children_spans_json.each do |child_span_json|
|
40
|
+
parent_id = child_span_json[:parent_id]
|
41
|
+
if parent_id == id
|
42
|
+
child_span_copy = child_span_json.dup
|
43
|
+
add_children_spans(child_span_copy, children_spans_json)
|
44
|
+
if span_json[:stacks].nil?
|
45
|
+
span_json[:stacks] = []
|
46
|
+
end
|
47
|
+
span_json[:stacks].push(child_span_copy)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# rubocop:disable Metrics/AbcSize
|
53
|
+
def build_span(span)
|
54
|
+
span_type = span.type
|
55
|
+
span_context = span.context && span.context.to_h
|
56
|
+
|
57
|
+
{
|
58
|
+
id: span.id,
|
59
|
+
parent_id: span.parent_id,
|
60
|
+
call: span_type,
|
61
|
+
reqBegin: span.relative_start,
|
62
|
+
reqEnd: span.relative_end,
|
63
|
+
props: span_context.nil? ? { CATEGORY: 'Ruby' } : span_context,
|
64
|
+
stacks: []
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
# rubocop:enable Metrics/AbcSize
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# This modules creates and initializes new Span
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'securerandom'
|
7
|
+
require 'stackify/span/context'
|
8
|
+
module StackifyRubyAPM
|
9
|
+
# @api private
|
10
|
+
class Span
|
11
|
+
DEFAULT_TYPE = 'custom'.freeze
|
12
|
+
|
13
|
+
# rubocop:disable Metrics/ParameterLists
|
14
|
+
def initialize(
|
15
|
+
transaction,
|
16
|
+
id,
|
17
|
+
name,
|
18
|
+
type = nil,
|
19
|
+
parent_id: nil,
|
20
|
+
context: nil,
|
21
|
+
http_status: nil
|
22
|
+
)
|
23
|
+
@transaction = transaction
|
24
|
+
@id = id
|
25
|
+
@name = name
|
26
|
+
@type = type || DEFAULT_TYPE
|
27
|
+
@parent_id = parent_id
|
28
|
+
@context = context
|
29
|
+
@http_status = http_status
|
30
|
+
|
31
|
+
@stacktrace = nil
|
32
|
+
@original_backtrace = nil
|
33
|
+
end
|
34
|
+
# rubocop:enable Metrics/ParameterLists
|
35
|
+
|
36
|
+
attr_accessor :name, :type, :original_backtrace, :http_status
|
37
|
+
attr_reader :id, :context, :stacktrace, :duration, :parent_id, :relative_start, :relative_end
|
38
|
+
|
39
|
+
def start
|
40
|
+
@relative_start = (Time.now).to_f * 1000
|
41
|
+
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def done
|
46
|
+
@relative_end = (Time.now).to_f * 1000
|
47
|
+
@duration = (Time.now).to_f * 1000
|
48
|
+
self.original_backtrace = nil # release it
|
49
|
+
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def done?
|
54
|
+
!!duration
|
55
|
+
end
|
56
|
+
|
57
|
+
def running?
|
58
|
+
relative_start && !done?
|
59
|
+
end
|
60
|
+
|
61
|
+
def inspect
|
62
|
+
"<StackifyRubyAPM::Span id:#{id}" \
|
63
|
+
" name:#{name.inspect}" \
|
64
|
+
" type:#{type.inspect}" \
|
65
|
+
'>'
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module StackifyRubyAPM
|
3
|
+
class Span
|
4
|
+
# @api private
|
5
|
+
class Context
|
6
|
+
include NaivelyHashable
|
7
|
+
|
8
|
+
def initialize(**args)
|
9
|
+
args.each do |key, val|
|
10
|
+
send(:"#{key}=", val)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :CATEGORY,
|
15
|
+
:SUBCATEGORY,
|
16
|
+
:COMPONENT_CATEGORY,
|
17
|
+
:COMPONENT_DETAIL,
|
18
|
+
:SQL,
|
19
|
+
:MONGODB_COLLECTION,
|
20
|
+
:METHOD,
|
21
|
+
:URL,
|
22
|
+
:STATUS,
|
23
|
+
:PROVIDER
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# This module communicates and registers the additional or enabled outside library method
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'forwardable'
|
7
|
+
require 'stackify/util/inflector'
|
8
|
+
|
9
|
+
module StackifyRubyAPM
|
10
|
+
# @api private
|
11
|
+
module Spies
|
12
|
+
# @api private
|
13
|
+
class Registration
|
14
|
+
extend Forwardable
|
15
|
+
|
16
|
+
def initialize(const_name, require_paths, spy)
|
17
|
+
@const_name = const_name
|
18
|
+
@require_paths = Array(require_paths)
|
19
|
+
@spy = spy
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :const_name, :require_paths
|
23
|
+
|
24
|
+
def_delegator :@spy, :install
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.require_hooks
|
28
|
+
@require_hooks ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.installed
|
32
|
+
@installed ||= {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.register(*args)
|
36
|
+
registration = Registration.new(*args)
|
37
|
+
|
38
|
+
if safe_defined?(registration.const_name)
|
39
|
+
registration.install
|
40
|
+
installed[registration.const_name] = registration
|
41
|
+
else
|
42
|
+
register_require_hook registration
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.register_require_hook(registration)
|
47
|
+
registration.require_paths.each do |path|
|
48
|
+
require_hooks[path] = registration
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.hook_into(name)
|
53
|
+
return unless (registration = require_hooks[name])
|
54
|
+
return unless safe_defined?(registration.const_name)
|
55
|
+
|
56
|
+
installed[registration.const_name] = registration
|
57
|
+
registration.install
|
58
|
+
|
59
|
+
registration.require_paths.each do |path|
|
60
|
+
require_hooks.delete path
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.safe_defined?(const_name)
|
65
|
+
Util::Inflector.safe_constantize(const_name)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# @api private
|
71
|
+
module Kernel
|
72
|
+
private
|
73
|
+
|
74
|
+
alias require_without_apm require
|
75
|
+
|
76
|
+
def require(path)
|
77
|
+
res = require_without_apm(path)
|
78
|
+
|
79
|
+
begin
|
80
|
+
StackifyRubyAPM::Spies.hook_into(path)
|
81
|
+
rescue ::Exception => e
|
82
|
+
puts "Failed hooking into '#{path}'. Please report this at " \
|
83
|
+
'git@bitbucket.org:stackify/stackify-ruby-apm.git'
|
84
|
+
puts e.backtrace.join("\n")
|
85
|
+
end
|
86
|
+
|
87
|
+
res
|
88
|
+
end
|
89
|
+
end
|