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
@@ -2,12 +2,15 @@
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
8
10
  # @api private
9
11
  class MysqlAdapterSpy
10
12
  TYPE = 'db.sinatra_active_record.sql'.freeze
13
+ DEFAULT_PORT = 3306
11
14
  if ActiveRecord::VERSION::MAJOR.to_i >= 5
12
15
  def install
13
16
  ActiveRecord::ConnectionAdapters::MySQL::DatabaseStatements.class_eval do
@@ -22,14 +25,10 @@ module StackifyRubyAPM
22
25
  exec_update_without_apm(sql, name, binds)
23
26
  end
24
27
 
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
- )
28
+ payload = {sql: sql, binds: binds}
29
+ statement = query_variables(payload)
30
+ check_prepared_stmt(statement, payload)
31
+ ctx = Span::Context.new(statement)
33
32
 
34
33
  result = exec_update_without_apm(sql, name, binds)
35
34
 
@@ -45,14 +44,10 @@ module StackifyRubyAPM
45
44
  exec_delete_without_apm(sql, name, binds)
46
45
  end
47
46
 
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
- )
47
+ payload = {sql: sql, binds: binds}
48
+ statement = query_variables(payload)
49
+ check_prepared_stmt(statement, payload)
50
+ ctx = Span::Context.new(statement)
56
51
 
57
52
  result = exec_delete_without_apm(sql, name, binds)
58
53
 
@@ -69,22 +64,38 @@ module StackifyRubyAPM
69
64
  exec_query_without_apm(sql, name, binds)
70
65
  end
71
66
 
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
- )
67
+ payload = {sql: sql, binds: binds}
68
+ statement = query_variables(payload)
69
+ check_prepared_stmt(statement, payload)
70
+ ctx = Span::Context.new(statement)
80
71
 
81
72
  result = exec_query_without_apm(sql, name, binds)
82
-
83
73
  StackifyRubyAPM.span name, TYPE, context: ctx do
84
74
  return result
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] = 'mysql'
82
+ props[:SQL] = payload[:sql]
83
+ props[:URL] = get_host unless !get_host
84
+ props
85
+ end
86
+
87
+ def check_prepared_stmt(statement, payload)
88
+ if StackifyRubyAPM.agent.config.prefix_enabled
89
+ check_prepared_stmt_by_placeholder(payload[:sql].include?('?'), statement, payload)
90
+ end
91
+ end
92
+
93
+ def get_host
94
+ query_options = self.raw_connection.query_options.to_h
95
+ "#{query_options[:host]}:#{query_options[:port] || DEFAULT_PORT}"
96
+ rescue StandardError => error
97
+ nil
98
+ end
88
99
  end
89
100
  end
90
101
  else
@@ -110,6 +121,7 @@ module StackifyRubyAPM
110
121
  SQL: sql,
111
122
  PROVIDER: 'mysql'
112
123
  )
124
+ ctx[:URL] = get_host unless !get_host
113
125
 
114
126
  result = exec_query_without_apm(sql, name, binds)
115
127
 
@@ -133,6 +145,7 @@ module StackifyRubyAPM
133
145
  SQL: sql,
134
146
  PROVIDER: 'mysql'
135
147
  )
148
+ ctx[:URL] = get_host unless !get_host
136
149
 
137
150
  result = exec_delete_without_apm(sql, name, binds)
138
151
 
@@ -156,6 +169,7 @@ module StackifyRubyAPM
156
169
  SQL: sql,
157
170
  PROVIDER: 'mysql'
158
171
  )
172
+ ctx[:URL] = get_host unless !get_host
159
173
 
160
174
  result = exec_update_without_apm(sql, name, binds)
161
175
 
@@ -179,6 +193,7 @@ module StackifyRubyAPM
179
193
  SQL: sql,
180
194
  PROVIDER: 'mysql'
181
195
  )
196
+ ctx[:URL] = get_host unless !get_host
182
197
 
183
198
  result = exec_insert_without_apm(sql, name, binds, _pk = nil, _sequence_name = nil)
184
199
 
@@ -186,6 +201,14 @@ module StackifyRubyAPM
186
201
  return result
187
202
  end
188
203
  end
204
+
205
+ def get_host
206
+ config = self.config.to_h
207
+ "#{config[:host]}:#{config[:port] || DEFAULT_PORT}"
208
+ rescue StandardError => error
209
+ nil
210
+ end
211
+
189
212
  end
190
213
  end
191
214
  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,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,27 @@ 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[:URL] = get_host unless !get_host
84
+ props
85
+ end
86
+
87
+ def check_prepared_stmt(statement, payload)
88
+ if StackifyRubyAPM.agent.config.prefix_enabled
89
+ check_prepared_stmt_by_placeholder(!!payload[:sql].match(/\$\d/), statement, payload)
90
+ end
91
+ end
92
+
93
+ def get_host
94
+ connection = self.raw_connection
95
+ "#{connection.host}:#{connection.port}"
96
+ rescue StandardError => error
97
+ nil
98
+ end
88
99
  end
89
100
  end
90
101
  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
@@ -21,15 +23,11 @@ module StackifyRubyAPM
21
23
  exec_query_without_apm(sql, name, binds)
22
24
  end
23
25
 
24
- ctx = Span::Context.new(
25
- CATEGORY: 'Database',
26
- SUBCATEGORY: 'Execute',
27
- COMPONENT_CATEGORY: 'DB Query',
28
- COMPONENT_DETAIL: 'Execute SQL Query',
29
- SQL: sql,
30
- PROVIDER: 'generic'
31
- )
26
+ payload = {sql: sql, binds: binds}
27
+ statement = query_variables(payload)
28
+ check_prepared_stmt(statement, payload)
32
29
 
30
+ ctx = Span::Context.new(statement)
33
31
  result = exec_query_without_apm(sql, name, binds)
34
32
 
35
33
  StackifyRubyAPM.span name, TYPE, context: ctx do
@@ -37,6 +35,18 @@ module StackifyRubyAPM
37
35
  end
38
36
  end
39
37
  # rubocop:enable Lint/UnusedMethodArgument
38
+
39
+ def query_variables(payload)
40
+ props = get_common_db_properties
41
+ props[:SQL] = payload[:sql]
42
+ props
43
+ end
44
+
45
+ def check_prepared_stmt(statement, payload)
46
+ if StackifyRubyAPM.agent.config.prefix_enabled
47
+ check_prepared_stmt_by_placeholder(payload[:sql].include?('?'), statement, payload)
48
+ end
49
+ end
40
50
  end
41
51
  end
42
52
  end
@@ -14,15 +14,21 @@ module StackifyRubyAPM
14
14
  def log_message_task(level, msg, call_trace)
15
15
  return log_message_task_without_apm(level, msg, call_trace) unless StackifyRubyAPM.current_transaction
16
16
 
17
- trans_id = StackifyRubyAPM.current_transaction.id
18
- log_uuid = SecureRandom.uuid
17
+ begin
18
+ trans_id = StackifyRubyAPM.current_transaction.id
19
+ log_uuid = SecureRandom.uuid
19
20
 
20
- # build span context
21
- ctx = Span::Context.new(
22
- CATEGORY: 'Stackify',
23
- SUBCATEGORY: 'Log',
24
- ID: log_uuid # matching the MsgObject id
25
- )
21
+ # build span context
22
+ ctx = Span::Context.new(
23
+ CATEGORY: 'Stackify',
24
+ SUBCATEGORY: 'Log',
25
+ ID: log_uuid # matching the MsgObject id
26
+ )
27
+ rescue Exception => e
28
+ StackifyRubyAPM.agent.error "[StackifyLoggerSpy] Error: creating span context."
29
+ StackifyRubyAPM.agent.error "[StackifyLoggerSpy] #{e.inspect}"
30
+ return log_message_task_without_apm(level, msg, call_trace)
31
+ end
26
32
 
27
33
  # create log span
28
34
  StackifyRubyAPM.span(msg, 'stackify.log', context: ctx) do
@@ -34,15 +40,21 @@ module StackifyRubyAPM
34
40
  def log_exception_task(level, ex)
35
41
  return log_exception_task_without_apm(level, ex) unless StackifyRubyAPM.current_transaction
36
42
 
37
- trans_id = StackifyRubyAPM.agent.current_transaction.id
38
- log_uuid = SecureRandom.uuid
43
+ begin
44
+ trans_id = StackifyRubyAPM.agent.current_transaction.id
45
+ log_uuid = SecureRandom.uuid
39
46
 
40
- # build span context
41
- ctx = Span::Context.new(
42
- CATEGORY: 'Stackify',
43
- SUBCATEGORY: 'Log',
44
- ID: log_uuid # matching the MsgObject id
45
- )
47
+ # build span context
48
+ ctx = Span::Context.new(
49
+ CATEGORY: 'Stackify',
50
+ SUBCATEGORY: 'Log',
51
+ ID: log_uuid # matching the MsgObject id
52
+ )
53
+ rescue Exception => e
54
+ StackifyRubyAPM.agent.error "[StackifyLoggerSpy] Error: creating span context."
55
+ StackifyRubyAPM.agent.error "[StackifyLoggerSpy] #{e.inspect}"
56
+ return log_exception_task_without_apm(level, ex)
57
+ end
46
58
 
47
59
  # create log span
48
60
  StackifyRubyAPM.span(ex.message, 'stackify.log', context: ctx) do
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Monkey patch for the sucker_punch class for running async tasks.
4
+ #
5
+
6
+ module StackifyRubyAPM
7
+ # @api private
8
+ module Spies
9
+ # @api private
10
+ class SuckerPunchSpy
11
+ def install
12
+ SuckerPunch::Job::ClassMethods.class_eval do
13
+ alias_method '__run_perform_without_elastic_apm', '__run_perform'
14
+
15
+ def __run_perform(*args)
16
+ ret = nil
17
+ begin
18
+ name = "#{to_s}.perform"
19
+ ctx = StackifyRubyAPM::Context.new
20
+ ctx.category = 'SuckerPunch::Job'
21
+ transaction = StackifyRubyAPM.transaction name, 'TASK', context: ctx
22
+ ret = __run_perform_without_elastic_apm(*args)
23
+ rescue StackifyRubyAPM::InternalError
24
+ raise # Don't report StackifyRubyAPM errors
25
+ rescue StandardError => e
26
+ StackifyRubyAPM.report e
27
+ raise e
28
+ ensure
29
+ transaction.submit()
30
+ end
31
+ ret
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ register 'SuckerPunch', 'sucker_punch', SuckerPunchSpy.new
38
+ end
39
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Monkey patch for the Yell::Logger class for logging log messages.
4
+ #
5
+ module StackifyRubyAPM
6
+ # @api private
7
+ module Spies
8
+ # @api private
9
+ class YellSpy
10
+ SEVERITIES = ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL', 'UNKNOWN'].freeze
11
+
12
+ def install
13
+ Yell::Logger.module_eval do
14
+ alias_method 'add_without_apm', 'add'
15
+
16
+ # Log messages
17
+ def add(options, *messages, &block)
18
+ return add_without_apm(options, *messages, &block) unless StackifyRubyAPM.current_transaction
19
+
20
+ begin
21
+ name = 'yell;'
22
+ type = 'ext.yell'
23
+ log_message = []
24
+ exception = []
25
+
26
+ messages.each { |msg|
27
+ case msg
28
+ when ::String
29
+ log_message.push(msg)
30
+ when ::Exception
31
+ log_message.push(msg.message)
32
+ exception.push("(#{ msg.class })\n#{ msg.backtrace.join("\n") if msg.backtrace }")
33
+ else
34
+ log_message.push(msg.inspect)
35
+ end
36
+ }
37
+
38
+ ctx = Span::Context.new(
39
+ CATEGORY: 'Log',
40
+ SUBCATEGORY: 'Yell',
41
+ LEVEL: SEVERITIES[options.severity],
42
+ MESSAGE: log_message.to_s,
43
+ PREFIX: 'TRUE'
44
+ )
45
+
46
+ if exception.length > 0
47
+ ctx.EXCEPTION = exception.to_s
48
+ end
49
+ rescue Exception => e
50
+ StackifyRubyAPM.agent.error "[YellSpy] Error: creating span context."
51
+ StackifyRubyAPM.agent.error "[YellSpy] #{e.inspect}"
52
+ return add_without_apm(options, *messages, &block)
53
+ end
54
+
55
+ StackifyRubyAPM.span name, type, context: ctx do
56
+ add_without_apm(options, *messages, &block)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ register 'Yell', 'yell', YellSpy.new
64
+ end
65
+ end