stackify-ruby-apm 1.10.0 → 1.12.3

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/stackify_apm/agent.rb +0 -2
  3. data/lib/stackify_apm/config.rb +33 -5
  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 -1
  9. data/lib/stackify_apm/instrumenter_helper.rb +90 -4
  10. data/lib/stackify_apm/logger/log_device.rb +7 -5
  11. data/lib/stackify_apm/logger/logger_high_version.rb +5 -1
  12. data/lib/stackify_apm/logger/logger_lower_version.rb +5 -1
  13. data/lib/stackify_apm/middleware.rb +21 -3
  14. data/lib/stackify_apm/normalizers/active_record.rb +16 -8
  15. data/lib/stackify_apm/root_info.rb +7 -1
  16. data/lib/stackify_apm/serializers/transactions.rb +5 -0
  17. data/lib/stackify_apm/span/context.rb +39 -1
  18. data/lib/stackify_apm/spies.rb +4 -2
  19. data/lib/stackify_apm/spies/action_dispatch.rb +6 -1
  20. data/lib/stackify_apm/spies/curb.rb +41 -20
  21. data/lib/stackify_apm/spies/curb/easy.rb +220 -92
  22. data/lib/stackify_apm/spies/curb/multi.rb +26 -12
  23. data/lib/stackify_apm/spies/custom_instrumenter.rb +25 -4
  24. data/lib/stackify_apm/spies/delayed_job.rb +49 -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 +59 -0
  29. data/lib/stackify_apm/spies/logger.rb +116 -0
  30. data/lib/stackify_apm/spies/logging.rb +65 -0
  31. data/lib/stackify_apm/spies/net_http.rb +38 -20
  32. data/lib/stackify_apm/spies/redis.rb +36 -30
  33. data/lib/stackify_apm/spies/sequel.rb +28 -11
  34. data/lib/stackify_apm/spies/sinatra_activerecord/mysql_adapter.rb +27 -25
  35. data/lib/stackify_apm/spies/sinatra_activerecord/postgresql_adapter.rb +27 -24
  36. data/lib/stackify_apm/spies/sinatra_activerecord/sqlite_adapter.rb +18 -8
  37. data/lib/stackify_apm/spies/stackify_logger.rb +28 -16
  38. data/lib/stackify_apm/spies/yell.rb +64 -0
  39. data/lib/stackify_apm/util.rb +10 -9
  40. data/lib/stackify_apm/version.rb +1 -1
  41. data/stackify-ruby-apm.gemspec +1 -0
  42. metadata +23 -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
- # 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,47 @@ 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
+ redis_nspace = !redis_details.blank? ? redis_details[0] : ''
22
+ redis_key = ''
22
23
 
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]
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
- # 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
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
- return call_without_apm(command, &block) if command[0] == :auth
37
+ return call_without_apm(command, &block) if command[0] == :auth
37
38
 
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
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
- ctx = Span::Context.new(context)
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 = 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
@@ -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
- ctx = Span::Context.new(
26
- CATEGORY: 'Database',
27
- SUBCATEGORY: 'Execute',
28
- COMPONENT_CATEGORY: 'DB Query',
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
- ctx = Span::Context.new(
49
- CATEGORY: 'Database',
50
- SUBCATEGORY: 'Execute',
51
- COMPONENT_CATEGORY: 'DB Query',
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
- ctx = Span::Context.new(
73
- CATEGORY: 'Database',
74
- SUBCATEGORY: 'Execute',
75
- COMPONENT_CATEGORY: 'DB Query',
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
- ctx = Span::Context.new(
26
- CATEGORY: 'Database',
27
- SUBCATEGORY: 'Execute',
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
- ctx = Span::Context.new(
49
- CATEGORY: 'Database',
50
- SUBCATEGORY: 'Execute',
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
- ctx = Span::Context.new(
73
- CATEGORY: 'Database',
74
- SUBCATEGORY: 'Execute',
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