stackify-ruby-apm 1.11.1 → 1.12.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/stackify_apm/config.rb +32 -5
- 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 +38 -0
- data/lib/stackify_apm/instrumenter_helper.rb +87 -0
- data/lib/stackify_apm/middleware.rb +21 -3
- data/lib/stackify_apm/normalizers/active_record.rb +16 -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 +39 -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/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 +59 -0
- data/lib/stackify_apm/spies/logger.rb +116 -0
- data/lib/stackify_apm/spies/logging.rb +65 -0
- data/lib/stackify_apm/spies/net_http.rb +38 -20
- data/lib/stackify_apm/spies/redis.rb +36 -30
- data/lib/stackify_apm/spies/sequel.rb +28 -11
- data/lib/stackify_apm/spies/sinatra_activerecord/mysql_adapter.rb +27 -25
- data/lib/stackify_apm/spies/sinatra_activerecord/postgresql_adapter.rb +27 -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/yell.rb +64 -0
- data/lib/stackify_apm/util.rb +10 -0
- data/lib/stackify_apm/version.rb +1 -1
- metadata +8 -2
@@ -0,0 +1,65 @@
|
|
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
|
+
)
|
45
|
+
|
46
|
+
if exception
|
47
|
+
ctx.EXCEPTION = exception
|
48
|
+
end
|
49
|
+
rescue Exception => e
|
50
|
+
StackifyRubyAPM.agent.error "[LoggingSpy] Error: creating span context."
|
51
|
+
StackifyRubyAPM.agent.error "[LoggingSpy] #{e.inspect}"
|
52
|
+
return log_event_without_apm(event)
|
53
|
+
end
|
54
|
+
|
55
|
+
StackifyRubyAPM.span name, type, context: ctx do
|
56
|
+
log_event_without_apm(event)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
register 'Logging::Logger', 'logging', LoggingSpy.new
|
64
|
+
end
|
65
|
+
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,47 @@ 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
|
+
redis_nspace = !redis_details.blank? ? redis_details[0] : ''
|
22
|
+
redis_key = ''
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
# Checks CACHEKEY value
|
25
|
+
if !redis_details.blank? && redis_details[1]
|
26
|
+
# Initially sets the CACHEKEY value
|
27
|
+
args = redis_details[1].split('/')
|
28
|
+
redis_key = args[0]
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
# If command has passed __method__, it will be included in the CACHEKEY value
|
31
|
+
# Possible formats:
|
32
|
+
# `<namespace:key/method_name/expires_in=300/ttl=60/>`
|
33
|
+
# `<namespace:key/expires_in=300/ttl=60/>`
|
34
|
+
redis_key = args[0..1].join('/') if args.length > 1 && !args[1].include?('=')
|
35
|
+
end
|
35
36
|
|
36
|
-
|
37
|
+
return call_without_apm(command, &block) if command[0] == :auth
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
39
|
+
context = {
|
40
|
+
PROVIDER: 'Redis',
|
41
|
+
CATEGORY: 'Cache',
|
42
|
+
SUBCATEGORY: 'Execute',
|
43
|
+
COMPONENT_CATEGORY: 'Cache',
|
44
|
+
COMPONENT_DETAIL: 'Execute',
|
45
|
+
THREAD_ID: Thread.current.object_id,
|
46
|
+
OPERATION: name
|
47
|
+
}.tap do |hash|
|
48
|
+
hash[:CACHEKEY] = redis_key unless redis_key.empty?
|
49
|
+
hash[:CACHENAME] = redis_nspace unless redis_nspace.empty?
|
50
|
+
end
|
50
51
|
|
51
|
-
|
52
|
+
ctx = Span::Context.new(context)
|
53
|
+
rescue Exception => e
|
54
|
+
StackifyRubyAPM.agent.error "[RedisSpy] Error: creating span context."
|
55
|
+
StackifyRubyAPM.agent.error "[RedisSpy] #{e.inspect}"
|
56
|
+
return call_without_apm(command, &block)
|
57
|
+
end
|
52
58
|
|
53
59
|
StackifyRubyAPM.span name, type, context: ctx do
|
54
60
|
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
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
# Spies for active record when sinatra framework is used and active_record is being extended. (mysql adapter)
|
4
4
|
|
5
|
+
require 'stackify_apm/helper/database_helper'
|
6
|
+
|
5
7
|
module StackifyRubyAPM
|
6
8
|
# @api private
|
7
9
|
module Spies
|
@@ -22,14 +24,10 @@ module StackifyRubyAPM
|
|
22
24
|
exec_update_without_apm(sql, name, binds)
|
23
25
|
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
COMPONENT_DETAIL: 'Execute SQL Query',
|
30
|
-
SQL: sql,
|
31
|
-
PROVIDER: 'mysql'
|
32
|
-
)
|
27
|
+
payload = {sql: sql, binds: binds}
|
28
|
+
statement = query_variables(payload)
|
29
|
+
check_prepared_stmt(statement, payload)
|
30
|
+
ctx = Span::Context.new(statement)
|
33
31
|
|
34
32
|
result = exec_update_without_apm(sql, name, binds)
|
35
33
|
|
@@ -45,14 +43,10 @@ module StackifyRubyAPM
|
|
45
43
|
exec_delete_without_apm(sql, name, binds)
|
46
44
|
end
|
47
45
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
COMPONENT_DETAIL: 'Execute SQL Query',
|
53
|
-
SQL: sql,
|
54
|
-
PROVIDER: 'mysql'
|
55
|
-
)
|
46
|
+
payload = {sql: sql, binds: binds}
|
47
|
+
statement = query_variables(payload)
|
48
|
+
check_prepared_stmt(statement, payload)
|
49
|
+
ctx = Span::Context.new(statement)
|
56
50
|
|
57
51
|
result = exec_delete_without_apm(sql, name, binds)
|
58
52
|
|
@@ -69,22 +63,30 @@ module StackifyRubyAPM
|
|
69
63
|
exec_query_without_apm(sql, name, binds)
|
70
64
|
end
|
71
65
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
COMPONENT_DETAIL: 'Execute SQL Query',
|
77
|
-
SQL: sql,
|
78
|
-
PROVIDER: 'mysql'
|
79
|
-
)
|
66
|
+
payload = {sql: sql, binds: binds}
|
67
|
+
statement = query_variables(payload)
|
68
|
+
check_prepared_stmt(statement, payload)
|
69
|
+
ctx = Span::Context.new(statement)
|
80
70
|
|
81
71
|
result = exec_query_without_apm(sql, name, binds)
|
82
|
-
|
83
72
|
StackifyRubyAPM.span name, TYPE, context: ctx do
|
84
73
|
return result
|
85
74
|
end
|
86
75
|
end
|
87
76
|
# rubocop:enable Lint/UnusedMethodArgument
|
77
|
+
|
78
|
+
def query_variables(payload)
|
79
|
+
props = get_common_db_properties
|
80
|
+
props[:PROVIDER] = 'mysql'
|
81
|
+
props[:SQL] = payload[:sql]
|
82
|
+
props
|
83
|
+
end
|
84
|
+
|
85
|
+
def check_prepared_stmt(statement, payload)
|
86
|
+
if StackifyRubyAPM.agent.config.prefix_enabled
|
87
|
+
check_prepared_stmt_by_placeholder(payload[:sql].include?('?'), statement, payload)
|
88
|
+
end
|
89
|
+
end
|
88
90
|
end
|
89
91
|
end
|
90
92
|
else
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
# Spies for active record when sinatra framework is used and active_record is being extended. (mysql adapter)
|
4
4
|
|
5
|
+
require 'stackify_apm/helper/database_helper'
|
6
|
+
|
5
7
|
module StackifyRubyAPM
|
6
8
|
# @api private
|
7
9
|
module Spies
|
@@ -22,15 +24,11 @@ module StackifyRubyAPM
|
|
22
24
|
exec_update_without_apm(sql, name, binds)
|
23
25
|
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
COMPONENT_CATEGORY: 'DB Query',
|
29
|
-
COMPONENT_DETAIL: 'Execute SQL Query',
|
30
|
-
SQL: sql,
|
31
|
-
PROVIDER: 'postgresql'
|
32
|
-
)
|
27
|
+
payload = {sql: sql, binds: binds}
|
28
|
+
statement = query_variables(payload)
|
29
|
+
check_prepared_stmt(statement, payload)
|
33
30
|
|
31
|
+
ctx = Span::Context.new(statement)
|
34
32
|
result = exec_update_without_apm(sql, name, binds)
|
35
33
|
|
36
34
|
StackifyRubyAPM.span name, TYPE, context: ctx do
|
@@ -45,15 +43,11 @@ module StackifyRubyAPM
|
|
45
43
|
exec_delete_without_apm(sql, name, binds)
|
46
44
|
end
|
47
45
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
COMPONENT_CATEGORY: 'DB Query',
|
52
|
-
COMPONENT_DETAIL: 'Execute SQL Query',
|
53
|
-
SQL: sql,
|
54
|
-
PROVIDER: 'postgresql'
|
55
|
-
)
|
46
|
+
payload = {sql: sql, binds: binds}
|
47
|
+
statement = query_variables(payload)
|
48
|
+
check_prepared_stmt(statement, payload)
|
56
49
|
|
50
|
+
ctx = Span::Context.new(statement)
|
57
51
|
result = exec_delete_without_apm(sql, name, binds)
|
58
52
|
|
59
53
|
StackifyRubyAPM.span name, TYPE, context: ctx do
|
@@ -69,15 +63,11 @@ module StackifyRubyAPM
|
|
69
63
|
exec_query_without_apm(sql, name, binds)
|
70
64
|
end
|
71
65
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
COMPONENT_CATEGORY: 'DB Query',
|
76
|
-
COMPONENT_DETAIL: 'Execute SQL Query',
|
77
|
-
SQL: sql,
|
78
|
-
PROVIDER: 'postgresql'
|
79
|
-
)
|
66
|
+
payload = {sql: sql, binds: binds}
|
67
|
+
statement = query_variables(payload)
|
68
|
+
check_prepared_stmt(statement, payload)
|
80
69
|
|
70
|
+
ctx = Span::Context.new(statement)
|
81
71
|
result = exec_query_without_apm(sql, name, binds)
|
82
72
|
|
83
73
|
StackifyRubyAPM.span name, TYPE, context: ctx do
|
@@ -85,6 +75,19 @@ module StackifyRubyAPM
|
|
85
75
|
end
|
86
76
|
end
|
87
77
|
# rubocop:enable Lint/UnusedMethodArgument
|
78
|
+
|
79
|
+
def query_variables(payload)
|
80
|
+
props = get_common_db_properties
|
81
|
+
props[:PROVIDER] = 'postgresql'
|
82
|
+
props[:SQL] = payload[:sql]
|
83
|
+
props
|
84
|
+
end
|
85
|
+
|
86
|
+
def check_prepared_stmt(statement, payload)
|
87
|
+
if StackifyRubyAPM.agent.config.prefix_enabled
|
88
|
+
check_prepared_stmt_by_placeholder(!!payload[:sql].match(/\$\d/), statement, payload)
|
89
|
+
end
|
90
|
+
end
|
88
91
|
end
|
89
92
|
end
|
90
93
|
end
|