stackify-ruby-apm 1.10.4 → 1.14.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/stackify_apm/agent.rb +0 -2
  3. data/lib/stackify_apm/config.rb +36 -6
  4. data/lib/stackify_apm/context.rb +4 -1
  5. data/lib/stackify_apm/context/prefix.rb +30 -0
  6. data/lib/stackify_apm/context/request/headers.rb +30 -0
  7. data/lib/stackify_apm/context_builder.rb +1 -0
  8. data/lib/stackify_apm/helper/database_helper.rb +39 -0
  9. data/lib/stackify_apm/instrumenter_helper.rb +87 -0
  10. data/lib/stackify_apm/logger/logger_high_version.rb +5 -1
  11. data/lib/stackify_apm/logger/logger_lower_version.rb +5 -1
  12. data/lib/stackify_apm/middleware.rb +21 -3
  13. data/lib/stackify_apm/normalizers/active_record.rb +27 -8
  14. data/lib/stackify_apm/root_info.rb +6 -0
  15. data/lib/stackify_apm/serializers/transactions.rb +5 -0
  16. data/lib/stackify_apm/span/context.rb +42 -1
  17. data/lib/stackify_apm/spies.rb +4 -2
  18. data/lib/stackify_apm/spies/action_dispatch.rb +6 -1
  19. data/lib/stackify_apm/spies/curb.rb +41 -20
  20. data/lib/stackify_apm/spies/curb/easy.rb +220 -92
  21. data/lib/stackify_apm/spies/curb/multi.rb +26 -12
  22. data/lib/stackify_apm/spies/custom_instrumenter.rb +25 -4
  23. data/lib/stackify_apm/spies/dynamo_db.rb +51 -0
  24. data/lib/stackify_apm/spies/faraday.rb +87 -0
  25. data/lib/stackify_apm/spies/httparty.rb +45 -24
  26. data/lib/stackify_apm/spies/httpclient.rb +41 -20
  27. data/lib/stackify_apm/spies/httprb.rb +39 -18
  28. data/lib/stackify_apm/spies/log4r.rb +60 -0
  29. data/lib/stackify_apm/spies/logger.rb +117 -0
  30. data/lib/stackify_apm/spies/logging.rb +66 -0
  31. data/lib/stackify_apm/spies/mongo.rb +3 -1
  32. data/lib/stackify_apm/spies/net_http.rb +38 -20
  33. data/lib/stackify_apm/spies/redis.rb +39 -30
  34. data/lib/stackify_apm/spies/sequel.rb +28 -11
  35. data/lib/stackify_apm/spies/sinatra_activerecord/mysql_adapter.rb +48 -25
  36. data/lib/stackify_apm/spies/sinatra_activerecord/postgresql_adapter.rb +35 -24
  37. data/lib/stackify_apm/spies/sinatra_activerecord/sqlite_adapter.rb +18 -8
  38. data/lib/stackify_apm/spies/stackify_logger.rb +28 -16
  39. data/lib/stackify_apm/spies/sucker_punch.rb +39 -0
  40. data/lib/stackify_apm/spies/yell.rb +65 -0
  41. data/lib/stackify_apm/util.rb +10 -9
  42. data/lib/stackify_apm/version.rb +1 -1
  43. 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
@@ -55,7 +55,9 @@ module StackifyRubyAPM
55
55
  #
56
56
  ctx = Span::Context.new(
57
57
  CATEGORY: 'MongoDB',
58
- MONGODB_COLLECTION: col
58
+ MONGODB_COLLECTION: col,
59
+ OPERATION: event.command_name.to_s,
60
+ URL: event.address.to_s
59
61
  )
60
62
 
61
63
  # Creates new span from Mongo event
@@ -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
- # Data configuration
19
- #
20
- host, = req['host'] && req['host'].split(':')
21
- method = req.method
18
+ begin
19
+ # Data configuration
20
+ #
21
+ host, = req['host'] && req['host'].split(':')
22
+ method = req.method
22
23
 
23
- host ||= address
24
+ host ||= address
24
25
 
25
- # For Ruby version 2.2.x, 2.3.x, 2.4.x we need to parse it and get the <scheme> & <host>/<path>
26
- updated_uri = req.uri.scheme + '://' + req.uri.host + req.uri.path if defined?(req.uri.host)
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
- name = "#{method} #{host}"
29
- type = "ext.net_http.#{method}"
29
+ name = "#{method} #{host}"
30
+ type = "ext.net_http.#{method}"
30
31
 
31
- # Builds span context
32
- #
33
- ctx = Span::Context.new(
34
- CATEGORY: 'Web External',
35
- SUBCATEGORY: 'Execute',
36
- URL: req.uri.nil? ? host : updated_uri,
37
- STATUS: '',
38
- METHOD: method
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
- status_code = result.code.to_i
49
- ctx.update_status(status_code)
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
- name = command[0].upcase.to_s
18
- type = 'db.redis'
19
- redis_details = command[1].is_a?(String) ? command[1].split(':') : []
20
- redis_nspace = !redis_details.blank? ? redis_details[0] : ''
21
- redis_key = ''
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
- # Checks CACHEKEY value
24
- if !redis_details.blank? && redis_details[1]
25
- # Initially sets the CACHEKEY value
26
- args = redis_details[1].split('/')
27
- redis_key = args[0]
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
- # If command has passed __method__, it will be included in the CACHEKEY value
30
- # Possible formats:
31
- # `<namespace:key/method_name/expires_in=300/ttl=60/>`
32
- # `<namespace:key/expires_in=300/ttl=60/>`
33
- redis_key = args[0..1].join('/') if args.length > 1 && !args[1].include?('=')
34
- end
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
- return call_without_apm(command, &block) if command[0] == :auth
39
+ return call_without_apm(command, &block) if command[0] == :auth
37
40
 
38
- context = {
39
- PROVIDER: 'Redis',
40
- CATEGORY: 'Cache',
41
- SUBCATEGORY: 'Execute',
42
- COMPONENT_CATEGORY: 'Cache',
43
- COMPONENT_DETAIL: 'Execute',
44
- THREAD_ID: Thread.current.object_id,
45
- OPERATION: name
46
- }.tap do |hash|
47
- hash[:CACHEKEY] = redis_key unless redis_key.empty?
48
- hash[:CACHENAME] = redis_nspace unless redis_nspace.empty?
49
- end
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
- ctx = Span::Context.new(context)
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 = Span::Context.new(
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
- require 'stackify_apm/helper/database_helper'
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