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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d62649a9bf57a77665b23b7e42ffe0d19395853a9b0b79e128197691e5bfb62
4
- data.tar.gz: '0216963cac56d043591eb797ab64cce012a479de25d9a5fbe202eb6f3d73929a'
3
+ metadata.gz: ec496ca2f97d6531e0e5059845fb078d60e194f0b2896a5148cc570f8de66c13
4
+ data.tar.gz: 5dbc4a02c01e54b3d1276932f5d7528da2751d734a15926517595528b00dd0a8
5
5
  SHA512:
6
- metadata.gz: 5c6c501b6eac9dbad6378a03cf1234468c84480fe15b977e44187d331452c39f6a76d19348b9141616791e564ccc436c48f8d27900796ca06104a67863c5a02f
7
- data.tar.gz: 89edd6496518ed877c65e87320a8bbf543f60a34b7e9023e0546b011854077d1c31f656bccbf1947229831b21f1fdf4f352c984cf11931dd3dc1b8e15e17e102
6
+ metadata.gz: 76c13b39665912b23fa8bd42cb75af0cfa455e83735814f167c768f380b96f411f4267670b8708f15d0aa6de9698d58055fba04ba6f8286dfb1efe51fb724361
7
+ data.tar.gz: 2aa04cf139250db11e987a38ad213863a0d4dab9427561a1a2f63240a42005b37ab1dd9e8ae62df54159d6a05f9d7039df116fa41eea71f00ed69adfc173ebc3
@@ -101,8 +101,6 @@ module StackifyRubyAPM
101
101
  info '[Agent] start()'
102
102
  info '[Agent] transport type: ' + @config.transport
103
103
  spies_name = ''
104
- # If the rake task is detected as being ran then we don't load the spies
105
- StackifyRubyAPM::Util.apm_disabled_in_rake
106
104
  return false unless @config.instrument
107
105
  config.enabled_spies.each do |lib|
108
106
  spies_name = spies_name + ', ' + lib.inspect.to_s
@@ -26,7 +26,7 @@ module StackifyRubyAPM
26
26
  environment_name: ENV['RAILS_ENV'] || ENV['RACK_ENV'],
27
27
  already_instrumented_flag: false,
28
28
  rum_auto_injection: false,
29
- rum_enabled: false,
29
+ rum_enabled: true,
30
30
  rum_cookie_path: '/',
31
31
  rum_cookie_name: '.Stackify.Rum',
32
32
  transport: StackifyRubyAPM::TRACE_LOG,
@@ -70,7 +70,8 @@ module StackifyRubyAPM
70
70
  transport_http_endpoint: 'https://localhost:10601',
71
71
 
72
72
  queue: true,
73
- lambda_handler: ''
73
+ lambda_handler: '',
74
+ prefix_enabled: false
74
75
  }.freeze
75
76
 
76
77
  ENV_TO_KEY = {
@@ -97,7 +98,8 @@ module StackifyRubyAPM
97
98
  'STACKIFY_FLUSH_INTERVAL' => 'flush_interval_seconds',
98
99
  'STACKIFY_DISABLED_SPIES' => [:list, 'disabled_spies'],
99
100
  'STACKIFY_QUEUE' => [:bool, 'queue'],
100
- 'STACKIFY_LAMBDA_HANDLER' => 'lambda_handler'
101
+ 'STACKIFY_LAMBDA_HANDLER' => 'lambda_handler',
102
+ 'STACKIFY_PREFIX_ENABLED' => [:bool, 'prefix_enabled']
101
103
  }.freeze
102
104
 
103
105
  def initialize(options = {})
@@ -105,6 +107,7 @@ module StackifyRubyAPM
105
107
  set_from_args(options)
106
108
  set_from_config_file
107
109
  set_from_env
110
+ set_prefix_paths if @prefix_enabled
108
111
  yield self if block_given?
109
112
  debug_logger
110
113
  StackifyRubyAPM::Util.host_os == 'WINDOWS' ? load_stackify_props_windows : load_stackify_props_linux
@@ -170,6 +173,7 @@ module StackifyRubyAPM
170
173
 
171
174
  attr_accessor :queue
172
175
  attr_accessor :lambda_handler
176
+ attr_accessor :prefix_enabled
173
177
 
174
178
  attr_reader :client_id
175
179
  attr_reader :device_id
@@ -210,6 +214,17 @@ module StackifyRubyAPM
210
214
  httparty
211
215
  stackify_logger
212
216
  sidekiq
217
+ delayed_job
218
+ ]
219
+ end
220
+
221
+ def prefix_spies
222
+ return [] unless @prefix_enabled
223
+ %w[
224
+ logger
225
+ logging
226
+ log4r
227
+ yell
213
228
  ]
214
229
  end
215
230
 
@@ -232,7 +247,7 @@ module StackifyRubyAPM
232
247
  available_spies
233
248
  end
234
249
 
235
- new_available_spies - disabled_spies
250
+ new_available_spies + prefix_spies - disabled_spies
236
251
  end
237
252
 
238
253
  # Default Transport
@@ -253,7 +268,11 @@ module StackifyRubyAPM
253
268
 
254
269
  def assign(options)
255
270
  options.each do |key, value|
256
- send("#{key}=", value)
271
+ begin
272
+ send("#{key}=", value)
273
+ rescue Exception => e
274
+ info "[Config] Key: '#{key}' doesn't exist."
275
+ end
257
276
  end
258
277
  end
259
278
 
@@ -294,6 +313,14 @@ module StackifyRubyAPM
294
313
  assign(YAML.load_file(config_file) || {})
295
314
  end
296
315
 
316
+ # set log paths to prefix path if prefix_enabled is true
317
+ def set_prefix_paths
318
+ all_user_profile = ENV['ALLUSERSPROFILE'] || "C:\\ProgramData"
319
+
320
+ @log_path = StackifyRubyAPM::Util.host_os == 'WINDOWS' ? "#{all_user_profile}\\Stackify\\Agent\\debug\\stackify-ruby-apm-1.log" : "/usr/local/prefix/debug/stackify-ruby-apm-1.log"
321
+ @log_trace_path = StackifyRubyAPM::Util.host_os == 'WINDOWS' ? "#{all_user_profile}\\Stackify\\Agent\\log\\" : '/usr/local/prefix/log/'
322
+ end
323
+
297
324
  # rubocop:disable Naming/AccessorMethodName
298
325
  def set_rails(app)
299
326
  self.application_name ||= format_name(application_name || app.class.parent_name).strip
@@ -381,6 +408,7 @@ module StackifyRubyAPM
381
408
  info '[Config] environment_name must be String type.' unless @environment_name.is_a?(String) && defined?(@environment_name)
382
409
  info '[Config] transport must be String type.' unless @transport.is_a?(String) && defined?(@transport)
383
410
  info '[Config] Transport should be one of these values: [agent_socket, default, agent_http]. Should be a String.' if defined?(@transport) && !%w[agent_socket default agent_http].include?(@transport.downcase)
411
+ info '[Config] prefix_enabled must be Boolean type: true/false.' unless [TrueClass, FalseClass].include?(@prefix_enabled.class) && defined?(@prefix_enabled)
384
412
  end
385
413
  # rubocop:enable Metrics/CyclomaticComplexity
386
414
  # rubocop:enable Metrics/PerceivedComplexity
@@ -2,7 +2,9 @@
2
2
 
3
3
  # This class initializes the parameters and variables for the context of Transaction/Span
4
4
 
5
+ require 'stackify_apm/context/prefix'
5
6
  require 'stackify_apm/context/request'
7
+ require 'stackify_apm/context/request/headers'
6
8
  require 'stackify_apm/context/request/socket'
7
9
  require 'stackify_apm/context/request/url'
8
10
  require 'stackify_apm/context/response'
@@ -12,12 +14,13 @@ module StackifyRubyAPM
12
14
  class Context
13
15
  include NaivelyHashable
14
16
 
15
- attr_accessor :request, :response, :aws
17
+ attr_accessor :request, :response, :aws, :category, :prefix
16
18
  attr_reader :custom, :tags
17
19
 
18
20
  def initialize
19
21
  @custom = {}
20
22
  @tags = {}
23
+ @prefix = Context::Prefix.new
21
24
  end
22
25
 
23
26
  # add aws context to context instance
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StackifyRubyAPM
4
+ class Context
5
+ # @api private
6
+ class Prefix
7
+ include NaivelyHashable
8
+
9
+ attr_accessor :response_body, :request_body
10
+ attr_reader :response_headers, :request_headers
11
+
12
+ def request_headers=(headers)
13
+ @request_headers = to_json_list(headers)
14
+ end
15
+
16
+ def response_headers=(headers)
17
+ @response_headers = to_json_list(headers)
18
+ end
19
+
20
+ private
21
+ def to_json_list(header)
22
+ list_header = []
23
+ header.each do |key, value|
24
+ list_header << {key => value}
25
+ end
26
+ list_header.to_json
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StackifyRubyAPM
4
+ # @api private
5
+ class Context
6
+ # @api private
7
+ class Request
8
+ # @api private
9
+ class Headers
10
+ include NaivelyHashable
11
+
12
+ def initialize(req)
13
+ @values = build_headers req
14
+ end
15
+
16
+ attr_reader :values
17
+
18
+ def build_headers req
19
+ env = req.env
20
+ headers = Hash[*env.select {|k,v| k.start_with? 'HTTP_'}
21
+ .collect {|k,v| [k.sub(/^HTTP_/, ''), v]}
22
+ .collect {|k,v| [k.split('_').collect(&:capitalize).join('-'), v]}
23
+ .sort
24
+ .flatten]
25
+ return headers
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -27,6 +27,7 @@ module StackifyRubyAPM
27
27
  request.method = req.request_method
28
28
  request.url = Context::Request::Url.new(req).to_h
29
29
  request.body = get_body(req)
30
+ request.headers = Context::Request::Headers.new(req).values
30
31
 
31
32
  context
32
33
  end
@@ -18,10 +18,48 @@ module DatabaseHelper
18
18
  elsif driver.include? 'db2'
19
19
  'db2'
20
20
  elsif driver.include? 'sqlite'
21
- 'generic'
21
+ 'sqlite'
22
22
  end
23
23
  end
24
24
  # rubocop:enable Metrics/CyclomaticComplexity
25
25
  # rubocop:enable Metrics/PerceivedComplexity
26
+
27
+ # Check the prepared statement by placeholder if its valid
28
+ # placeholder - contains the prepared statement pattern.
29
+ # Placeholder for mysql, sqlite, jdbc oracle, db2 is ?, example "SELECT * FROM posts WHERE author = ? and id = ?"
30
+ # Placeholder for postgres is $1, $2,...$n, example "SELECT * FROM posts WHERE author = $1 and id = $2"
31
+ # statement - contains the db properties such as SQL, PROVIDER, etc.
32
+ # So if there's payload value we append SQL_PARAMETERS to the existing object properties.
33
+ # payload - contains the payload[:binds] which is the payload value/data of the placeholder.
34
+ # Example payload: {:sql=>"SELECT * FROM posts WHERE author = ? and id = ?", :db_adapter=>"mysql2::client", :binds=>["J.K. Rowling", 1]}
35
+ def check_prepared_stmt_by_placeholder(placeholder, statement, payload)
36
+ sqlParam = []
37
+ unless payload[:binds].nil?
38
+ payload[:binds].each_with_index do |record, idx|
39
+ if record && defined?(record.value)
40
+ StackifyRubyAPM::Util.pushToAryIndex(sqlParam, idx, record.value)
41
+ elsif ((record && record.instance_of?(Array)) && defined?(record[1]))
42
+ StackifyRubyAPM::Util.pushToAryIndex(sqlParam, idx, record[1])
43
+ else
44
+ StackifyRubyAPM::Util.pushToAryIndex(sqlParam, idx, record)
45
+ end
46
+ end
47
+ statement[:SQL_PARAMETERS] = sqlParam.to_json if sqlParam.count > 0
48
+ true
49
+ else
50
+ false
51
+ end
52
+ end
53
+
54
+ # Common DB span properties shared to sequel, activerecord, etc.
55
+ def get_common_db_properties
56
+ {
57
+ CATEGORY: 'Database',
58
+ SUBCATEGORY: 'Execute',
59
+ COMPONENT_CATEGORY: 'DB Query',
60
+ COMPONENT_DETAIL: 'Execute SQL Query',
61
+ PROVIDER: 'generic'
62
+ }
63
+ end
26
64
  end
27
65
  include DatabaseHelper
@@ -90,9 +90,8 @@ module StackifyRubyAPM
90
90
  )
91
91
  end
92
92
 
93
- req = #{current_method_without}(*args, &block)
94
93
  StackifyRubyAPM.span name, type, context: ctx do
95
- req
94
+ #{current_method_without}(*args, &block)
96
95
  end
97
96
  else
98
97
  return #{current_method_without}(*args, &block)
@@ -169,9 +168,9 @@ module StackifyRubyAPM
169
168
  CATEGORY: 'Ruby'
170
169
  )
171
170
  end
172
- req = _self_without_apm_#{current_method}(*args, &block)
171
+
173
172
  StackifyRubyAPM.span name, type, context: ctx do
174
- req
173
+ _self_without_apm_#{current_method}(*args, &block)
175
174
  end
176
175
  else
177
176
  return _self_without_apm_#{current_method}(*args, &block)
@@ -190,6 +189,93 @@ module StackifyRubyAPM
190
189
  @custom_class_info[current_class.to_s]['controller'] = true if class_path && class_path.include?('controllers')
191
190
  @custom_class_info[current_class.to_s]['model'] = true if class_path && class_path.include?('models')
192
191
  end
192
+
193
+ # Monkey patch the single ruby module file.
194
+ #
195
+ # tracked_func - trackedFunction variable in stackify.json
196
+ # current_module - module variable in stackify.json
197
+ # class_path - file_path variable in stackify.json
198
+ # options - other options such as trackedFunctionName
199
+ def self.patched_module(tracked_func, current_module, class_path, **options)
200
+ current_method = options[:current_method] || nil
201
+ tracked_function_name = options[:tracked_function_name] || nil
202
+ transaction = options[:is_transaction] || nil
203
+ mod_spy = "#{current_module}Spy"
204
+
205
+ unless @custom_instrumented[current_module.to_s]
206
+ @custom_instrumented[current_module.to_s] = {}
207
+ end
208
+
209
+ unless @custom_instrumented[current_module.to_s][current_method.to_s]
210
+ @custom_instrumented[current_module.to_s][current_method.to_s] = false
211
+ end
212
+
213
+ unless @custom_class_info[current_module.to_s]
214
+ @custom_class_info[current_module.to_s] = {}
215
+ end
216
+
217
+ unless @custom_class_info[current_module.to_s]['controller']
218
+ @custom_class_info[current_module.to_s]['controller'] = false
219
+ end
220
+
221
+ unless @custom_class_info[current_module.to_s]['model']
222
+ @custom_class_info[current_module.to_s]['model'] = false
223
+ end
224
+
225
+ return unless @custom_instrumented[current_module.to_s][current_method.to_s] == false
226
+
227
+ eval <<-RUBY
228
+ module #{mod_spy}
229
+ def self.install
230
+ #{current_module}.class_eval do
231
+ class<< self
232
+ alias_method "#{current_method}_without_apm", "#{current_method}"
233
+
234
+ def #{current_method}(*args, &block)
235
+ if StackifyRubyAPM.current_transaction.nil? && #{!transaction.nil?}
236
+ t = StackifyRubyAPM.transaction("custom.#{current_module}.#{current_method}", TRACETYPE)
237
+ begin
238
+ req = #{current_method}_without_apm(*args, &block)
239
+ rescue Exception => e
240
+ StackifyRubyAPM.report(e)
241
+ raise e
242
+ ensure
243
+ t.submit
244
+ end
245
+ return req
246
+ elsif StackifyRubyAPM.current_transaction
247
+ name = "Custom Instrument"
248
+ type = "#{current_module}##{current_method}"
249
+ ctx = if "#{tracked_func}" == 'true'
250
+ Span::Context.new(
251
+ CATEGORY: 'Ruby',
252
+ TRACKED_FUNC: "#{tracked_function_name}"
253
+ )
254
+ else
255
+ Span::Context.new(
256
+ CATEGORY: 'Ruby'
257
+ )
258
+ end
259
+
260
+ StackifyRubyAPM.span name, type, context: ctx do
261
+ #{current_method}_without_apm(*args, &block)
262
+ end
263
+ else
264
+ return #{current_method}_without_apm(*args, &block)
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end
271
+
272
+ StackifyRubyAPM::Spies.register current_module.to_s, class_path.to_s, #{mod_spy}, true, "#{current_method}", "#{tracked_func}", "#{tracked_function_name}"
273
+ RUBY
274
+ @custom_instrumented[current_module.to_s][current_method.to_s] = true
275
+ @custom_class_info[current_module.to_s]['controller'] = true if class_path && class_path.include?('controllers')
276
+ @custom_class_info[current_module.to_s]['model'] = true if class_path && class_path.include?('models')
277
+ end
278
+
193
279
  # rubocop:enable Metrics/CyclomaticComplexity
194
280
  # rubocop:enable Metrics/PerceivedComplexity
195
281
  # rubocop:enable Lint/UselessAssignment
@@ -14,9 +14,6 @@ module StackifyRubyAPM
14
14
  if FileTest.exist?(@filename)
15
15
  else
16
16
  @dev = create_logfile(@filename)
17
- File.chmod(0o777, @filename)
18
-
19
- return @dev
20
17
  end
21
18
  end
22
19
  write_without_apm(message)
@@ -58,7 +55,6 @@ module StackifyRubyAPM
58
55
  temp_newfilename = "#{temp_filename}-#{filename_counter}.log"
59
56
  @filename = temp_newfilename
60
57
  @dev = create_logfile(@filename)
61
- File.chmod(0o777, @filename)
62
58
 
63
59
  true
64
60
  end
@@ -80,10 +76,16 @@ module StackifyRubyAPM
80
76
  @dev.close rescue nil
81
77
  File.rename(@filename.to_s, age_file)
82
78
  @dev = create_logfile(@filename)
83
- File.chmod(0o777, @filename)
84
79
 
85
80
  true
86
81
  end
82
+
83
+ # Override create_logfile of core LogDevice class where we set File.chmod to 0o777
84
+ def create_logfile(filename)
85
+ logdev = super
86
+ File.chmod(0o777, filename)
87
+ logdev
88
+ end
87
89
  # rubocop:enable Style/RescueModifier
88
90
  # rubocop:enable Lint/RescueWithoutErrorClass
89
91
  end
@@ -29,7 +29,11 @@ module StackifyRubyAPM
29
29
  temp_filename = logdev.gsub('-1.log', '')
30
30
  new_logdev = temp_filename + '-1.log'
31
31
  end
32
- @logdev = LogDevice.new(new_logdev, shift_size: shift_size)
32
+ begin
33
+ @logdev = LogDevice.new(new_logdev, shift_size: shift_size)
34
+ rescue StandardError => e
35
+ puts "Stackify Profiler unable to access [" + logdev + "]"
36
+ end
33
37
  end
34
38
  end
35
39
  require 'stackify_apm/logger/log_device'
@@ -20,7 +20,11 @@ module StackifyRubyAPM
20
20
  temp_filename = logdev.gsub('-1.log', '')
21
21
  new_logdev = temp_filename + '-1.log'
22
22
  end
23
- @logdev = LogDevice.new(new_logdev, shift_size: shift_size)
23
+ begin
24
+ @logdev = LogDevice.new(new_logdev, shift_size: shift_size)
25
+ rescue StandardError => e
26
+ puts "Stackify Profiler unable to access [" + logdev + "]"
27
+ end
24
28
  end
25
29
  end
26
30