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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/stackify_apm/config.rb +32 -5
  3. data/lib/stackify_apm/context.rb +4 -1
  4. data/lib/stackify_apm/context/prefix.rb +30 -0
  5. data/lib/stackify_apm/context/request/headers.rb +30 -0
  6. data/lib/stackify_apm/context_builder.rb +1 -0
  7. data/lib/stackify_apm/helper/database_helper.rb +38 -0
  8. data/lib/stackify_apm/instrumenter_helper.rb +87 -0
  9. data/lib/stackify_apm/middleware.rb +21 -3
  10. data/lib/stackify_apm/normalizers/active_record.rb +16 -8
  11. data/lib/stackify_apm/root_info.rb +6 -0
  12. data/lib/stackify_apm/serializers/transactions.rb +5 -0
  13. data/lib/stackify_apm/span/context.rb +39 -1
  14. data/lib/stackify_apm/spies.rb +4 -2
  15. data/lib/stackify_apm/spies/action_dispatch.rb +6 -1
  16. data/lib/stackify_apm/spies/curb.rb +41 -20
  17. data/lib/stackify_apm/spies/curb/easy.rb +220 -92
  18. data/lib/stackify_apm/spies/curb/multi.rb +26 -12
  19. data/lib/stackify_apm/spies/custom_instrumenter.rb +25 -4
  20. data/lib/stackify_apm/spies/httparty.rb +45 -24
  21. data/lib/stackify_apm/spies/httpclient.rb +41 -20
  22. data/lib/stackify_apm/spies/httprb.rb +39 -18
  23. data/lib/stackify_apm/spies/log4r.rb +59 -0
  24. data/lib/stackify_apm/spies/logger.rb +116 -0
  25. data/lib/stackify_apm/spies/logging.rb +65 -0
  26. data/lib/stackify_apm/spies/net_http.rb +38 -20
  27. data/lib/stackify_apm/spies/redis.rb +36 -30
  28. data/lib/stackify_apm/spies/sequel.rb +28 -11
  29. data/lib/stackify_apm/spies/sinatra_activerecord/mysql_adapter.rb +27 -25
  30. data/lib/stackify_apm/spies/sinatra_activerecord/postgresql_adapter.rb +27 -24
  31. data/lib/stackify_apm/spies/sinatra_activerecord/sqlite_adapter.rb +18 -8
  32. data/lib/stackify_apm/spies/stackify_logger.rb +28 -16
  33. data/lib/stackify_apm/spies/yell.rb +64 -0
  34. data/lib/stackify_apm/util.rb +10 -0
  35. data/lib/stackify_apm/version.rb +1 -1
  36. 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
- # 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