stackify-ruby-apm 1.11.0 → 1.14.2
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 +4 -4
- data/lib/stackify_apm/config.rb +36 -6
- data/lib/stackify_apm/context.rb +4 -1
- data/lib/stackify_apm/context/prefix.rb +30 -0
- data/lib/stackify_apm/context/request/headers.rb +30 -0
- data/lib/stackify_apm/context_builder.rb +1 -0
- data/lib/stackify_apm/helper/database_helper.rb +39 -0
- data/lib/stackify_apm/instrumenter_helper.rb +87 -0
- data/lib/stackify_apm/logger/logger_high_version.rb +5 -1
- data/lib/stackify_apm/logger/logger_lower_version.rb +5 -1
- data/lib/stackify_apm/middleware.rb +23 -3
- data/lib/stackify_apm/normalizers/active_record.rb +27 -8
- data/lib/stackify_apm/root_info.rb +6 -0
- data/lib/stackify_apm/serializers/transactions.rb +5 -0
- data/lib/stackify_apm/span/context.rb +42 -1
- data/lib/stackify_apm/spies.rb +4 -2
- data/lib/stackify_apm/spies/action_dispatch.rb +6 -1
- data/lib/stackify_apm/spies/curb.rb +41 -20
- data/lib/stackify_apm/spies/curb/easy.rb +220 -92
- data/lib/stackify_apm/spies/curb/multi.rb +26 -12
- data/lib/stackify_apm/spies/custom_instrumenter.rb +25 -4
- data/lib/stackify_apm/spies/dynamo_db.rb +51 -0
- data/lib/stackify_apm/spies/faraday.rb +87 -0
- data/lib/stackify_apm/spies/httparty.rb +45 -24
- data/lib/stackify_apm/spies/httpclient.rb +41 -20
- data/lib/stackify_apm/spies/httprb.rb +39 -18
- data/lib/stackify_apm/spies/log4r.rb +60 -0
- data/lib/stackify_apm/spies/logger.rb +117 -0
- data/lib/stackify_apm/spies/logging.rb +66 -0
- data/lib/stackify_apm/spies/mongo.rb +3 -1
- data/lib/stackify_apm/spies/net_http.rb +38 -20
- data/lib/stackify_apm/spies/redis.rb +39 -30
- data/lib/stackify_apm/spies/sequel.rb +28 -11
- data/lib/stackify_apm/spies/sinatra_activerecord/mysql_adapter.rb +48 -25
- data/lib/stackify_apm/spies/sinatra_activerecord/postgresql_adapter.rb +35 -24
- data/lib/stackify_apm/spies/sinatra_activerecord/sqlite_adapter.rb +18 -8
- data/lib/stackify_apm/spies/stackify_logger.rb +28 -16
- data/lib/stackify_apm/spies/sucker_punch.rb +39 -0
- data/lib/stackify_apm/spies/yell.rb +65 -0
- data/lib/stackify_apm/util.rb +10 -0
- data/lib/stackify_apm/version.rb +1 -1
- metadata +11 -2
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Monkey patch for the Log4r::Outputter class for logging log message.
|
4
|
+
#
|
5
|
+
module StackifyRubyAPM
|
6
|
+
module Spies
|
7
|
+
class Log4rSpy
|
8
|
+
def install
|
9
|
+
Log4r::Outputter.module_eval do
|
10
|
+
alias_method 'canonical_log_without_apm', 'canonical_log'
|
11
|
+
|
12
|
+
# Log message formatted to a logevent object
|
13
|
+
def canonical_log(logevent)
|
14
|
+
return canonical_log_without_apm(logevent) unless StackifyRubyAPM.current_transaction
|
15
|
+
|
16
|
+
begin
|
17
|
+
name = 'log4r'
|
18
|
+
type = 'ext.log4r'
|
19
|
+
log_message = ''
|
20
|
+
exception = nil
|
21
|
+
msg = logevent.data
|
22
|
+
|
23
|
+
case msg
|
24
|
+
when ::String
|
25
|
+
log_message = msg
|
26
|
+
when ::Exception
|
27
|
+
log_message = msg.message
|
28
|
+
exception = "(#{ msg.class })\n#{ msg.backtrace.join("\n") if msg.backtrace }"
|
29
|
+
else
|
30
|
+
log_message = msg.inspect
|
31
|
+
end
|
32
|
+
|
33
|
+
ctx = Span::Context.new(
|
34
|
+
CATEGORY: 'Log',
|
35
|
+
SUBCATEGORY: 'Log4r',
|
36
|
+
LEVEL: Log4r::LNAMES[logevent.level] || 'ANY',
|
37
|
+
MESSAGE: log_message,
|
38
|
+
PREFIX: 'TRUE'
|
39
|
+
)
|
40
|
+
|
41
|
+
if exception
|
42
|
+
ctx.EXCEPTION = exception
|
43
|
+
end
|
44
|
+
rescue Exception => e
|
45
|
+
StackifyRubyAPM.agent.error "[Log4rSpy] Error: creating span context."
|
46
|
+
StackifyRubyAPM.agent.error "[Log4rSpy] #{e.inspect}"
|
47
|
+
return canonical_log_without_apm(logevent)
|
48
|
+
end
|
49
|
+
|
50
|
+
StackifyRubyAPM.span name, type, context: ctx do
|
51
|
+
canonical_log_without_apm(logevent)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
register 'Log4r::Outputter', 'log4r', Log4rSpy.new
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Monkey patch for the Logger class for logging log messages.
|
5
|
+
#
|
6
|
+
module StackifyRubyAPM
|
7
|
+
module Spies
|
8
|
+
class LoggerSpy
|
9
|
+
NAME = 'logger'
|
10
|
+
TYPE = 'core.devlog'
|
11
|
+
|
12
|
+
def install
|
13
|
+
Logger.class_eval do
|
14
|
+
alias_method 'debug_without_apm', 'debug'
|
15
|
+
alias_method 'info_without_apm', 'info'
|
16
|
+
alias_method 'warn_without_apm', 'warn'
|
17
|
+
alias_method 'error_without_apm', 'error'
|
18
|
+
alias_method 'fatal_without_apm', 'fatal'
|
19
|
+
alias_method 'unknown_without_apm', 'unknown'
|
20
|
+
|
21
|
+
def debug(progname = nil, &block)
|
22
|
+
return debug_without_apm(progname, &block) unless StackifyRubyAPM.current_transaction
|
23
|
+
ctx = StackifyRubyAPM::Spies::LoggerSpy.get_log_context('DEBUG', nil, progname, &block)
|
24
|
+
StackifyRubyAPM.span NAME, TYPE, context: ctx do
|
25
|
+
debug_without_apm(progname, &block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def info(progname = nil, &block)
|
30
|
+
return info_without_apm(progname, &block) unless StackifyRubyAPM.current_transaction
|
31
|
+
ctx = StackifyRubyAPM::Spies::LoggerSpy.get_log_context('INFO', nil, progname, &block)
|
32
|
+
StackifyRubyAPM.span NAME, TYPE, context: ctx do
|
33
|
+
info_without_apm(progname, &block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def warn(progname = nil, &block)
|
38
|
+
return warn_without_apm(progname, &block) unless StackifyRubyAPM.current_transaction
|
39
|
+
ctx = StackifyRubyAPM::Spies::LoggerSpy.get_log_context('WARN', nil, progname, &block)
|
40
|
+
StackifyRubyAPM.span NAME, TYPE, context: ctx do
|
41
|
+
warn_without_apm(progname, &block)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def error(progname = nil, &block)
|
46
|
+
return error_without_apm(progname, &block) unless StackifyRubyAPM.current_transaction
|
47
|
+
ctx = StackifyRubyAPM::Spies::LoggerSpy.get_log_context('ERROR', nil, progname, &block)
|
48
|
+
StackifyRubyAPM.span NAME, TYPE, context: ctx do
|
49
|
+
error_without_apm(progname, &block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def fatal(progname = nil, &block)
|
54
|
+
return fatal_without_apm(progname, &block) unless StackifyRubyAPM.current_transaction
|
55
|
+
ctx = StackifyRubyAPM::Spies::LoggerSpy.get_log_context('FATAL', nil, progname, &block)
|
56
|
+
StackifyRubyAPM.span NAME, TYPE, context: ctx do
|
57
|
+
fatal_without_apm(progname, &block)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def unknown(progname = nil, &block)
|
62
|
+
return unknown_without_apm(progname, &block) unless StackifyRubyAPM.current_transaction
|
63
|
+
ctx = StackifyRubyAPM::Spies::LoggerSpy.get_log_context('UNKNOWN', nil, progname, &block)
|
64
|
+
StackifyRubyAPM.span NAME, TYPE, context: ctx do
|
65
|
+
unknown_without_apm(progname, &block)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# create and return logger span context
|
72
|
+
def self.get_log_context(severity, message, progname)
|
73
|
+
ctx = nil
|
74
|
+
|
75
|
+
begin
|
76
|
+
log_message = ''
|
77
|
+
exception = nil
|
78
|
+
|
79
|
+
if message.nil?
|
80
|
+
msg = progname
|
81
|
+
else
|
82
|
+
msg = message
|
83
|
+
end
|
84
|
+
|
85
|
+
case msg
|
86
|
+
when ::String
|
87
|
+
log_message = msg
|
88
|
+
when ::Exception
|
89
|
+
log_message = msg.message
|
90
|
+
exception = "(#{ msg.class })\n#{ msg.backtrace.join("\n") if msg.backtrace }"
|
91
|
+
else
|
92
|
+
log_message = msg.inspect
|
93
|
+
end
|
94
|
+
|
95
|
+
ctx = Span::Context.new(
|
96
|
+
CATEGORY: 'Log',
|
97
|
+
SUBCATEGORY: 'Logger',
|
98
|
+
LEVEL: severity || 'ANY',
|
99
|
+
MESSAGE: log_message,
|
100
|
+
PREFIX: 'TRUE'
|
101
|
+
)
|
102
|
+
|
103
|
+
if exception
|
104
|
+
ctx.EXCEPTION = exception
|
105
|
+
end
|
106
|
+
rescue Exception => e
|
107
|
+
StackifyRubyAPM.agent.error "[LoggerSpy] Error: creating span context."
|
108
|
+
StackifyRubyAPM.agent.error "[LoggerSpy] #{e.inspect}"
|
109
|
+
end
|
110
|
+
|
111
|
+
ctx
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
register 'Logger', 'logger', LoggerSpy.new
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Monkey patch for the Logging::Logger class for logging log messages.
|
5
|
+
#
|
6
|
+
module StackifyRubyAPM
|
7
|
+
module Spies
|
8
|
+
class LoggingSpy
|
9
|
+
LVL_LABEL = %w(DEBUG INFO WARN ERROR FATAL ANY).freeze
|
10
|
+
|
11
|
+
def install
|
12
|
+
Logging::Logger.class_eval do
|
13
|
+
alias_method 'log_event_without_apm', 'log_event'
|
14
|
+
|
15
|
+
# Log message formatted to an event object
|
16
|
+
def log_event(event)
|
17
|
+
return log_event_without_apm(event) unless StackifyRubyAPM.current_transaction
|
18
|
+
|
19
|
+
begin
|
20
|
+
name = 'logging'
|
21
|
+
type = 'ext.logging'
|
22
|
+
log_message = ''
|
23
|
+
exception = nil
|
24
|
+
|
25
|
+
obj = event.data
|
26
|
+
|
27
|
+
case obj
|
28
|
+
when String;
|
29
|
+
log_message = obj
|
30
|
+
when Exception
|
31
|
+
log_message = obj.message
|
32
|
+
exception = "(#{ obj.class.name })\n#{ obj.backtrace.join("\n") if obj.backtrace }"
|
33
|
+
when nil;
|
34
|
+
log_message = "(#{obj.class.name}) nil"
|
35
|
+
else
|
36
|
+
log_message = obj.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
ctx = Span::Context.new(
|
40
|
+
CATEGORY: 'Log',
|
41
|
+
SUBCATEGORY: 'Logging',
|
42
|
+
LEVEL: LVL_LABEL[event.level] || 'ANY',
|
43
|
+
MESSAGE: log_message,
|
44
|
+
PREFIX: 'TRUE'
|
45
|
+
)
|
46
|
+
|
47
|
+
if exception
|
48
|
+
ctx.EXCEPTION = exception
|
49
|
+
end
|
50
|
+
rescue Exception => e
|
51
|
+
StackifyRubyAPM.agent.error "[LoggingSpy] Error: creating span context."
|
52
|
+
StackifyRubyAPM.agent.error "[LoggingSpy] #{e.inspect}"
|
53
|
+
return log_event_without_apm(event)
|
54
|
+
end
|
55
|
+
|
56
|
+
StackifyRubyAPM.span name, type, context: ctx do
|
57
|
+
log_event_without_apm(event)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
register 'Logging::Logger', 'logging', LoggingSpy.new
|
65
|
+
end
|
66
|
+
end
|
@@ -15,28 +15,34 @@ module StackifyRubyAPM
|
|
15
15
|
return request_without_apm(req, body, &block) unless StackifyRubyAPM.current_transaction
|
16
16
|
return request_without_apm(req, body, &block) if started? && req['span']
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
begin
|
19
|
+
# Data configuration
|
20
|
+
#
|
21
|
+
host, = req['host'] && req['host'].split(':')
|
22
|
+
method = req.method
|
22
23
|
|
23
|
-
|
24
|
+
host ||= address
|
24
25
|
|
25
|
-
|
26
|
-
|
26
|
+
# For Ruby version 2.2.x, 2.3.x, 2.4.x we need to parse it and get the <scheme> & <host>/<path>
|
27
|
+
updated_uri = req.uri.scheme + '://' + req.uri.host + req.uri.path if defined?(req.uri.host)
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
name = "#{method} #{host}"
|
30
|
+
type = "ext.net_http.#{method}"
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
32
|
+
# Builds span context
|
33
|
+
#
|
34
|
+
ctx = Span::Context.new(
|
35
|
+
CATEGORY: 'Web External',
|
36
|
+
SUBCATEGORY: 'Execute',
|
37
|
+
URL: req.uri.nil? ? host : updated_uri,
|
38
|
+
STATUS: '',
|
39
|
+
METHOD: method
|
40
|
+
)
|
41
|
+
rescue Exception => e
|
42
|
+
StackifyRubyAPM.agent.error "[NetHTTPSpy] Error: creating span context."
|
43
|
+
StackifyRubyAPM.agent.error "[NetHTTPSpy] #{e.inspect}"
|
44
|
+
return request_without_apm(req, body, &block)
|
45
|
+
end
|
40
46
|
|
41
47
|
# Creates new span from HTTP result
|
42
48
|
#
|
@@ -45,8 +51,20 @@ module StackifyRubyAPM
|
|
45
51
|
#
|
46
52
|
req['span'] = true
|
47
53
|
result = request_without_apm(req, body, &block)
|
48
|
-
|
49
|
-
|
54
|
+
begin
|
55
|
+
status_code = result.code.to_i
|
56
|
+
ctx.update_status(status_code)
|
57
|
+
|
58
|
+
if StackifyRubyAPM.agent.config.prefix_enabled
|
59
|
+
ctx.update_request_body(req.body || "")
|
60
|
+
ctx.update_request_headers(req.each_header || Hash.new)
|
61
|
+
ctx.update_response_body(result.body || "")
|
62
|
+
ctx.update_response_headers(result.each_header || Hash.new)
|
63
|
+
end
|
64
|
+
rescue Exception => e
|
65
|
+
StackifyRubyAPM.agent.error '[NetHTTPSpy] Error: getting status code or updating request/response context.'
|
66
|
+
StackifyRubyAPM.agent.error "[NetHTTPSpy] #{e.inspect}"
|
67
|
+
end
|
50
68
|
return result
|
51
69
|
end
|
52
70
|
end
|
@@ -14,41 +14,50 @@ module StackifyRubyAPM
|
|
14
14
|
alias_method 'call_without_apm', 'call'
|
15
15
|
|
16
16
|
def call(command, &block)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
begin
|
18
|
+
name = command[0].upcase.to_s
|
19
|
+
type = 'db.redis'
|
20
|
+
redis_details = command[1].is_a?(String) ? command[1].split(':') : []
|
21
|
+
# use length instead of .blank?
|
22
|
+
# reason: will throw error if activesupport missing
|
23
|
+
redis_nspace = redis_details.length ? redis_details[0] : ''
|
24
|
+
redis_key = ''
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
# Checks CACHEKEY value
|
27
|
+
if redis_details.length && redis_details[1]
|
28
|
+
# Initially sets the CACHEKEY value
|
29
|
+
args = redis_details[1].split('/')
|
30
|
+
redis_key = args[0]
|
28
31
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
# If command has passed __method__, it will be included in the CACHEKEY value
|
33
|
+
# Possible formats:
|
34
|
+
# `<namespace:key/method_name/expires_in=300/ttl=60/>`
|
35
|
+
# `<namespace:key/expires_in=300/ttl=60/>`
|
36
|
+
redis_key = args[0..1].join('/') if args.length > 1 && !args[1].include?('=')
|
37
|
+
end
|
35
38
|
|
36
|
-
|
39
|
+
return call_without_apm(command, &block) if command[0] == :auth
|
37
40
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
41
|
+
context = {
|
42
|
+
PROVIDER: 'Redis',
|
43
|
+
CATEGORY: 'Cache',
|
44
|
+
SUBCATEGORY: 'Execute',
|
45
|
+
COMPONENT_CATEGORY: 'Cache',
|
46
|
+
COMPONENT_DETAIL: 'Execute',
|
47
|
+
THREAD_ID: Thread.current.object_id,
|
48
|
+
OPERATION: name,
|
49
|
+
URL: "#{self.options[:host]}:#{self.options[:port]}"
|
50
|
+
}.tap do |hash|
|
51
|
+
hash[:CACHEKEY] = redis_key unless redis_key.empty?
|
52
|
+
hash[:CACHENAME] = redis_nspace unless redis_nspace.empty?
|
53
|
+
end
|
50
54
|
|
51
|
-
|
55
|
+
ctx = Span::Context.new(context)
|
56
|
+
rescue Exception => e
|
57
|
+
StackifyRubyAPM.agent.error "[RedisSpy] Error: creating span context."
|
58
|
+
StackifyRubyAPM.agent.error "[RedisSpy] #{e.inspect}"
|
59
|
+
return call_without_apm(command, &block)
|
60
|
+
end
|
52
61
|
|
53
62
|
StackifyRubyAPM.span name, type, context: ctx do
|
54
63
|
call_without_apm(command, &block)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'stackify_apm/helper/database_helper'
|
4
|
+
|
3
5
|
#
|
4
6
|
# This class monkey patch the sequel.rb gem with the request method.
|
5
7
|
#
|
@@ -33,17 +35,12 @@ module StackifyRubyAPM
|
|
33
35
|
name = summarize sql
|
34
36
|
db_adapter = ''
|
35
37
|
db_adapter = args[0].class.to_s.gsub('::Database', '').downcase if args[0]
|
38
|
+
payload = {sql: sql, db_adapter: db_adapter, binds: defined?(args[1]) ? args[1]: []}
|
39
|
+
statement = query_variables(payload)
|
40
|
+
check_prepared_stmt(statement, payload)
|
41
|
+
ctx = Span::Context.new(statement)
|
36
42
|
|
37
|
-
context
|
38
|
-
CATEGORY: 'Database',
|
39
|
-
SUBCATEGORY: 'Execute',
|
40
|
-
COMPONENT_CATEGORY: 'DB Query',
|
41
|
-
COMPONENT_DETAIL: 'Execute SQL Query',
|
42
|
-
SQL: sql,
|
43
|
-
PROVIDER: get_profiler(db_adapter)
|
44
|
-
)
|
45
|
-
|
46
|
-
StackifyRubyAPM.span(name, TYPE, context: context, &block)
|
43
|
+
StackifyRubyAPM.span(name, TYPE, context: ctx, &block)
|
47
44
|
end
|
48
45
|
|
49
46
|
# rubocop:disable Lint/UselessAssignment
|
@@ -55,7 +52,27 @@ module StackifyRubyAPM
|
|
55
52
|
end
|
56
53
|
end || DEFAULT
|
57
54
|
end
|
58
|
-
|
55
|
+
|
56
|
+
def query_variables(payload)
|
57
|
+
props = get_common_db_properties
|
58
|
+
props[:PROVIDER] = get_profiler(payload[:db_adapter])
|
59
|
+
props[:SQL] = payload[:sql]
|
60
|
+
props
|
61
|
+
end
|
62
|
+
|
63
|
+
def check_prepared_stmt(statement, payload)
|
64
|
+
if StackifyRubyAPM.agent.config.prefix_enabled
|
65
|
+
case get_profiler(payload[:db_adapter])
|
66
|
+
when 'generic', 'mysql', 'sqlite', 'oracle', 'db2'
|
67
|
+
# We check the placeholder of sequel mysql by ? and sometimes sequel sqlite is using : or ?
|
68
|
+
if check_prepared_stmt_by_placeholder(payload[:sql].include?('?'), statement, payload)
|
69
|
+
else check_prepared_stmt_by_placeholder(payload[:sql].include?(':'), statement, payload)
|
70
|
+
end
|
71
|
+
when 'postgresql'
|
72
|
+
check_prepared_stmt_by_placeholder(!!payload[:sql].match(/\$\d/), statement, payload)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
59
76
|
end
|
60
77
|
# rubocop:enable Lint/UselessAssignment
|
61
78
|
end
|