stackify-ruby-apm 1.0.1 → 1.1.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +38 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +17 -11
  5. data/Gemfile.lock +98 -95
  6. data/Rakefile +7 -5
  7. data/docker/stackify-ruby +8 -0
  8. data/docker/stackify-ruby-rvm +10 -0
  9. data/docker/stackify-ruby-test +28 -0
  10. data/lib/{stackify → stackify_apm}/agent.rb +42 -33
  11. data/lib/{stackify → stackify_apm}/config.rb +56 -39
  12. data/lib/{stackify → stackify_apm}/context.rb +5 -6
  13. data/lib/{stackify → stackify_apm}/context/request.rb +0 -0
  14. data/lib/{stackify → stackify_apm}/context/request/socket.rb +0 -0
  15. data/lib/{stackify → stackify_apm}/context/request/url.rb +2 -6
  16. data/lib/stackify_apm/context/response.rb +33 -0
  17. data/lib/{stackify → stackify_apm}/context_builder.rb +2 -5
  18. data/lib/{stackify → stackify_apm}/error.rb +7 -6
  19. data/lib/stackify_apm/error/exception.rb +37 -0
  20. data/lib/stackify_apm/error/log.rb +24 -0
  21. data/lib/stackify_apm/error_builder.rb +61 -0
  22. data/lib/stackify_apm/helper/database_helper.rb +27 -0
  23. data/lib/{stackify → stackify_apm}/instrumenter.rb +12 -19
  24. data/lib/{stackify → stackify_apm}/internal_error.rb +0 -0
  25. data/lib/{stackify → stackify_apm}/log.rb +0 -0
  26. data/lib/{stackify → stackify_apm}/logger/log_device.rb +22 -11
  27. data/lib/{stackify → stackify_apm}/logger/logger_high_version.rb +1 -6
  28. data/lib/{stackify → stackify_apm}/logger/logger_lower_version.rb +2 -1
  29. data/lib/stackify_apm/middleware.rb +70 -0
  30. data/lib/{stackify → stackify_apm}/naively_hashable.rb +1 -3
  31. data/lib/{stackify → stackify_apm}/normalizers.rb +3 -2
  32. data/lib/{stackify → stackify_apm}/normalizers/action_controller.rb +0 -0
  33. data/lib/{stackify → stackify_apm}/normalizers/action_mailer.rb +0 -0
  34. data/lib/{stackify → stackify_apm}/normalizers/action_view.rb +0 -0
  35. data/lib/{stackify → stackify_apm}/normalizers/active_record.rb +3 -25
  36. data/lib/{stackify → stackify_apm}/railtie.rb +5 -7
  37. data/lib/{stackify → stackify_apm}/root_info.rb +2 -6
  38. data/lib/{stackify → stackify_apm}/serializers.rb +3 -2
  39. data/lib/{stackify → stackify_apm}/serializers/errors.rb +7 -10
  40. data/lib/{stackify → stackify_apm}/serializers/transactions.rb +11 -18
  41. data/lib/{stackify → stackify_apm}/span.rb +8 -9
  42. data/lib/{stackify → stackify_apm}/span/context.rb +3 -1
  43. data/lib/{stackify → stackify_apm}/spies.rb +3 -2
  44. data/lib/{stackify → stackify_apm}/spies/action_dispatch.rb +3 -4
  45. data/lib/stackify_apm/spies/curb.rb +49 -0
  46. data/lib/stackify_apm/spies/curb/easy.rb +157 -0
  47. data/lib/stackify_apm/spies/curb/multi.rb +43 -0
  48. data/lib/{stackify → stackify_apm}/spies/httpclient.rb +10 -8
  49. data/lib/{stackify → stackify_apm}/spies/httprb.rb +7 -9
  50. data/lib/{stackify → stackify_apm}/spies/mongo.rb +5 -3
  51. data/lib/{stackify → stackify_apm}/spies/net_http.rb +4 -5
  52. data/lib/{stackify → stackify_apm}/spies/redis.rb +19 -18
  53. data/lib/stackify_apm/spies/sequel.rb +65 -0
  54. data/lib/{stackify → stackify_apm}/spies/sinatra.rb +7 -10
  55. data/lib/stackify_apm/spies/sinatra_activerecord/mysql_adapter.rb +201 -0
  56. data/lib/stackify_apm/spies/sinatra_activerecord/postgresql_adapter.rb +94 -0
  57. data/lib/stackify_apm/spies/sinatra_activerecord/sqlite_adapter.rb +46 -0
  58. data/lib/stackify_apm/spies/stackify_logger.rb +60 -0
  59. data/lib/{stackify → stackify_apm}/spies/tilt.rb +3 -3
  60. data/lib/stackify_apm/stacktrace.rb +18 -0
  61. data/lib/stackify_apm/stacktrace/frame.rb +47 -0
  62. data/lib/{stackify → stackify_apm}/stacktrace_builder.rb +10 -11
  63. data/lib/{stackify → stackify_apm}/subscriber.rb +20 -14
  64. data/lib/{stackify → stackify_apm}/trace_logger.rb +10 -16
  65. data/lib/stackify_apm/transaction.rb +127 -0
  66. data/lib/{stackify → stackify_apm}/util.rb +3 -1
  67. data/lib/{stackify → stackify_apm}/util/dig.rb +1 -1
  68. data/lib/{stackify → stackify_apm}/util/inflector.rb +0 -0
  69. data/lib/{stackify → stackify_apm}/util/inspector.rb +1 -3
  70. data/lib/stackify_apm/util/lru_cache.rb +49 -0
  71. data/lib/stackify_apm/util/trace_log_watcher.rb +37 -0
  72. data/lib/stackify_apm/version.rb +6 -0
  73. data/lib/{stackify → stackify_apm}/worker.rb +8 -7
  74. data/lib/stackify_ruby_apm.rb +18 -15
  75. data/run-test-docker.sh +50 -0
  76. data/run-test.sh +1 -3
  77. data/stackify-ruby-apm.gemspec +14 -11
  78. metadata +86 -59
  79. data/lib/stackify/context/response.rb +0 -37
  80. data/lib/stackify/error/exception.rb +0 -36
  81. data/lib/stackify/error/log.rb +0 -25
  82. data/lib/stackify/error_builder.rb +0 -65
  83. data/lib/stackify/middleware.rb +0 -74
  84. data/lib/stackify/spies/sinatra_activerecord/mysql_adapter.rb +0 -177
  85. data/lib/stackify/spies/sinatra_activerecord/postgresql_adapter.rb +0 -96
  86. data/lib/stackify/spies/sinatra_activerecord/sqlite_adapter.rb +0 -48
  87. data/lib/stackify/stacktrace.rb +0 -19
  88. data/lib/stackify/stacktrace/frame.rb +0 -50
  89. data/lib/stackify/transaction.rb +0 -132
  90. data/lib/stackify/util/lru_cache.rb +0 -49
  91. data/lib/stackify/version.rb +0 -4
@@ -0,0 +1,28 @@
1
+ ARG from_version
2
+
3
+ FROM stackify-ruby-${from_version}:latest
4
+
5
+ ARG version
6
+
7
+ RUN mkdir -p /usr/local/stackify/stackify-ruby-apm/log
8
+ RUN mkdir /build
9
+ COPY . /build/
10
+
11
+ RUN /bin/bash --login -c "\
12
+ echo $version && \
13
+ rvm use ruby-$version && \
14
+ ruby -v && \
15
+ gem install bundler -v '~> 1.16' && \
16
+ gem install tzinfo-data && \
17
+ cd build && \
18
+ ls -l && \
19
+ bundle install \
20
+ "
21
+ CMD /bin/bash --login -c "\
22
+ echo $version && \
23
+ echo ${version} && \
24
+ rvm use ruby-$version && \
25
+ ruby -v && \
26
+ cd build && \
27
+ bundle exec rspec spec/ --format documentation --fail-fast \
28
+ "
@@ -1,20 +1,21 @@
1
1
  # frozen_string_literal: true
2
- #
2
+
3
3
  # The Agent interacts with the Web Application and Profiler.
4
4
  # It is also responsible in requesting the instrumenter to build the transactions and spans.
5
- #
6
-
7
- require 'stackify/naively_hashable'
8
- require 'stackify/context_builder'
9
- require 'stackify/error_builder'
10
- require 'stackify/stacktrace_builder'
11
- require 'stackify/error'
12
- require 'stackify/trace_logger'
13
- require 'stackify/spies'
14
- require 'stackify/serializers'
15
- require 'stackify/worker'
5
+
6
+ require 'stackify_apm/naively_hashable'
7
+ require 'stackify_apm/context_builder'
8
+ require 'stackify_apm/error_builder'
9
+ require 'stackify_apm/stacktrace_builder'
10
+ require 'stackify_apm/error'
11
+ require 'stackify_apm/trace_logger'
12
+ require 'stackify_apm/spies'
13
+ require 'stackify_apm/serializers'
14
+ require 'stackify_apm/worker'
15
+ require 'stackify_apm/util/trace_log_watcher'
16
16
 
17
17
  module StackifyRubyAPM
18
+ # @api private
18
19
  class Agent
19
20
  include Log
20
21
 
@@ -24,24 +25,29 @@ module StackifyRubyAPM
24
25
  @instance
25
26
  end
26
27
 
28
+ # rubocop:disable Metrics/CyclomaticComplexity
27
29
  def self.start(config)
28
30
  return @instance if @instance
31
+
29
32
  config = Config.new(config) unless config.is_a?(Config)
30
33
  pid = $PID || Process.pid
31
34
  host_name = config.hostname || `hostname`
32
35
  date_now = Time.now
33
- current_trace_file = config.log_trace_path + host_name.squish! + "#" + pid.to_s.squish! + ".log"
36
+ current_trace_file = config.log_trace_path + squish(host_name) + '#' + squish(pid.to_s) + '.log'
34
37
 
35
- if(ENV['STACKIFY_RUBY_ENV'] != 'rspec')
38
+ if ENV['STACKIFY_RUBY_ENV'] != 'rspec'
36
39
  config.tracer_logger = StackifyLogger.new(current_trace_file, config.filenum_rotate, config.logger_byte_size)
37
- config.logtime_created = date_now.strftime("%H:%M")
38
- end
39
-
40
+ config.logtime_created = date_now.strftime('%H:%M')
41
+ Util::TraceLogWatcher.delete_trace_logs(config)
42
+ end
43
+
40
44
  LOCK.synchronize do
41
45
  return @instance if @instance
46
+
42
47
  @instance = new(config).start
43
48
  end
44
49
  end
50
+ # rubocop:enable Metrics/CyclomaticComplexity
45
51
 
46
52
  def self.stop
47
53
  LOCK.synchronize do
@@ -59,7 +65,7 @@ module StackifyRubyAPM
59
65
  end
60
66
 
61
67
  def self.running?
62
- !!@instance
68
+ !@instance.nil?
63
69
  end
64
70
 
65
71
  def initialize(config)
@@ -89,14 +95,13 @@ module StackifyRubyAPM
89
95
  spies_name = ''
90
96
  config.enabled_spies.each do |lib|
91
97
  spies_name = spies_name + ', ' + lib.inspect.to_s
92
- require "stackify/spies/#{lib}"
98
+ require "stackify_apm/spies/#{lib}"
93
99
  end
94
100
  debug '[Agent] Loaded spies:' + spies_name
95
101
  self
96
102
  end
97
103
 
98
104
  # queues
99
-
100
105
  # Stores transaction in queue
101
106
  #
102
107
  def enqueue_transaction(transaction)
@@ -151,11 +156,8 @@ module StackifyRubyAPM
151
156
  exception,
152
157
  handled: handled
153
158
  )
154
- current_error = @error_serializer.build(error)
155
- if current_transaction
156
- current_transaction.add_exception(current_error)
157
- end
158
-
159
+ current_error = @error_serializer.build(error)
160
+ current_transaction.add_exception(current_error) if current_transaction
159
161
  end
160
162
 
161
163
  def report_message(message, backtrace: nil, **attrs)
@@ -167,6 +169,16 @@ module StackifyRubyAPM
167
169
  enqueue_error error
168
170
  end
169
171
 
172
+ # This method will strip and remove garbage character and multiple spaces.
173
+
174
+ def self.squish(str)
175
+ str = str.gsub(/\A[[:space:]]+/, '')
176
+ str = str.gsub(/[[:space:]]+\z/, '')
177
+ str = str.gsub(/[[:space:]]+/, ' ')
178
+ str = str.strip.gsub(/\s+/, '')
179
+ str
180
+ end
181
+
170
182
  private
171
183
 
172
184
  def boot_worker
@@ -174,20 +186,17 @@ module StackifyRubyAPM
174
186
 
175
187
  @worker_thread = Thread.new do
176
188
  Worker.new(
177
- config,
178
- messages,
179
- pending_transactions,
180
- trace_logger
189
+ config,
190
+ messages,
191
+ pending_transactions,
192
+ trace_logger
181
193
  ).run_forever
182
194
  end
183
195
  end
184
196
 
185
197
  def kill_worker
186
198
  messages << Worker::StopMsg.new
187
-
188
- if @worker_thread && !@worker_thread.join(5) # 5 secs
189
- raise 'Failed to wait for worker, not all messages sent'
190
- end
199
+ raise 'Failed to wait for worker, not all messages sent' if @worker_thread && !@worker_thread.join(5) # 5 secs
191
200
 
192
201
  @worker_thread = nil
193
202
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  #
3
4
  # The Config class sets the APM's default values.
4
5
  #
@@ -7,7 +8,6 @@ require 'logger'
7
8
  require 'yaml'
8
9
  require 'socket'
9
10
  module StackifyRubyAPM
10
- # rubocop:disable Metrics/ClassLength
11
11
  # @api private
12
12
  class Config
13
13
  include Log
@@ -15,26 +15,26 @@ module StackifyRubyAPM
15
15
  config_file: 'config/stackify_apm.yml',
16
16
  environment_name: ENV['RAILS_ENV'] || ENV['RACK_ENV'],
17
17
  instrument: true,
18
-
19
18
  log_path: '/usr/local/stackify/stackify-ruby-apm/log/stackify-ruby-apm.log',
20
19
  log_level: Logger::INFO,
21
-
22
20
  log_trace_path: '/usr/local/stackify/stackify-ruby-apm/log/',
23
21
 
24
22
  max_queue_size: 500, # Maximum queue length of transactions before sending transactions to the APM.
25
23
  flush_interval: 1, # interval with which transactions should be sent to the APM. Default value: 10 seconds
26
-
24
+
27
25
  filter_exception_types: [],
28
-
26
+
29
27
  tracer_logger: nil,
30
- logger_byte_size: 50000000,
28
+ logger_byte_size: 50_000_000, # e.g, 50_000_000=50mb
31
29
  filenum_rotate: 20,
32
- debugger_byte_size: 20000000,
30
+ debugger_byte_size: 20_000_000,
33
31
  debugger_filenum_rotate: 3,
34
32
  logtime_created: 0,
35
33
  http_status: nil,
36
34
  debug_transactions: false,
37
-
35
+ trace_log_age_in_hour: 60 * 60, # e.g, 60*60=1hour, 60*2=2mins
36
+ check_trace_log_per_hour: '1h', # e.g, 1h=every 1hour, 2m=every 2mins
37
+
38
38
  source_lines_error_app_frames: 5,
39
39
  source_lines_span_app_frames: 5,
40
40
  source_lines_error_library_frames: 0,
@@ -94,6 +94,8 @@ module StackifyRubyAPM
94
94
  attr_accessor :filenum_rotate
95
95
  attr_accessor :debugger_byte_size
96
96
  attr_accessor :debugger_filenum_rotate
97
+ attr_accessor :trace_log_age_in_hour
98
+ attr_accessor :check_trace_log_per_hour
97
99
 
98
100
  attr_accessor :tracer_logger
99
101
  attr_accessor :logtime_created
@@ -118,8 +120,8 @@ module StackifyRubyAPM
118
120
  attr_accessor :root_path
119
121
  attr_accessor :http_status
120
122
 
121
- attr_reader :clientId
122
- attr_reader :deviceId
123
+ attr_reader :client_id
124
+ attr_reader :device_id
123
125
 
124
126
  def app=(app)
125
127
  case app_type?(app)
@@ -132,13 +134,15 @@ module StackifyRubyAPM
132
134
  end
133
135
 
134
136
  def app_type?(app)
135
- if defined?(::Rails) && app.is_a?(Rails::Application)
136
- return :rails
137
- end
137
+ # if defined?(::Rails) && app.is_a?(Rails::Application)
138
+ # return :rails
139
+ # end
140
+
141
+ return :rails if defined?(::Rails) && app.is_a?(Rails::Application)
142
+
138
143
  nil
139
144
  end
140
145
 
141
- # rubocop:disable Metrics/MethodLength
142
146
  # available spies to use when framework is rails
143
147
  def available_spies
144
148
  %w[
@@ -147,26 +151,35 @@ module StackifyRubyAPM
147
151
  net_http
148
152
  httpclient
149
153
  redis
154
+ sequel
150
155
  tilt
151
156
  httprb
157
+ curb
158
+ curb/easy
159
+ curb/multi
160
+ stackify_logger
152
161
  ]
153
162
  end
154
- # rubocop:enable Metrics/MethodLength
155
163
 
156
164
  def enabled_spies
157
165
  # check if the framework is rails or sinatra
158
- sinatra_spies = %w[
166
+ sinatra_activerecord_spies = %w[
159
167
  sinatra
160
168
  sinatra_activerecord/mysql_adapter
161
169
  sinatra_activerecord/postgresql_adapter
162
170
  sinatra_activerecord/sqlite_adapter
163
171
  ]
164
- if (defined?(::Sinatra::Base))
165
- new_available_spies = available_spies + sinatra_spies
166
- else
167
- new_available_spies = available_spies
168
- end
169
-
172
+
173
+ new_available_spies = if defined? ::Sinatra::Base
174
+ if defined? ActiveRecord
175
+ available_spies + sinatra_activerecord_spies
176
+ else
177
+ available_spies
178
+ end
179
+ else
180
+ available_spies
181
+ end
182
+
170
183
  new_available_spies - disabled_spies
171
184
  end
172
185
 
@@ -182,7 +195,7 @@ module StackifyRubyAPM
182
195
  assign(DEFAULTS)
183
196
  end
184
197
 
185
- # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
198
+ # rubocop:disable Metrics/CyclomaticComplexity
186
199
  def set_from_env
187
200
  ENV_TO_KEY.each do |env_key, key|
188
201
  next unless (value = ENV[env_key])
@@ -201,30 +214,35 @@ module StackifyRubyAPM
201
214
  send("#{key}=", value)
202
215
  end
203
216
  end
204
- # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
217
+ # rubocop:enable Metrics/CyclomaticComplexity
205
218
 
219
+ # rubocop:disable Naming/AccessorMethodName
206
220
  def set_from_args(options)
207
221
  assign(options)
208
222
  end
223
+ # rubocop:enable Naming/AccessorMethodName
209
224
 
210
225
  def set_from_config_file
211
226
  return unless File.exist?(config_file)
227
+
212
228
  assign(YAML.load_file(config_file) || {})
213
229
  end
214
230
 
215
- def set_rails(app) # rubocop:disable Metrics/AbcSize
231
+ # rubocop:disable Naming/AccessorMethodName
232
+ def set_rails(app)
216
233
  self.application_name ||= format_name(application_name || app.class.parent_name)
217
234
  self.logger ||= Rails.logger
218
235
 
219
236
  self.root_path = Rails.root.to_s
220
237
  self.view_paths = app.config.paths['app/views'].existent
221
238
  end
239
+ # rubocop:enable Naming/AccessorMethodName
222
240
 
223
241
  def build_logger
224
242
  debuger_logpath = log_path == '-' ? $stdout : log_path
225
243
  logger = StackifyLogger.new(debuger_logpath, debugger_filenum_rotate, debugger_byte_size)
226
244
  logger.level = log_level
227
- self.logger = logger
245
+ self.logger = logger
228
246
  end
229
247
 
230
248
  def format_name(str)
@@ -232,26 +250,25 @@ module StackifyRubyAPM
232
250
  end
233
251
 
234
252
  def load_stackify_props
235
- @clientId = nil
236
- @deviceId = nil
253
+ @client_id = nil
254
+ @device_id = nil
237
255
  begin
238
- warn "Reading the stackify.properties file"
239
- IO.foreach(@stackify_properties_file) { |line|
256
+ info 'Reading the stackify.properties file'
257
+ IO.foreach(@stackify_properties_file) do |line|
240
258
  case line.downcase
241
259
  when /clientid=\d+/
242
- @clientId = line.split('=')[1].strip
260
+ @client_id = line.split('=')[1].strip
243
261
  when /deviceid=\d+/
244
- @deviceId = line.split('=')[1].strip
262
+ @device_id = line.split('=')[1].strip
245
263
  end
246
- }
247
- warn "stackify.properties: clientId=#{@clientId}, deviceId=#{@deviceId}"
264
+ end
265
+ info "stackify.properties: clientId=#{@client_id}, deviceId=#{@device_id}"
248
266
  rescue StandardError => e
249
- warn "Error occured while reading the stackify.properties file."
250
- warn e.inspect
267
+ info 'Error occured while reading the stackify.properties file.'
268
+ info e.inspect
251
269
  end
252
- warn "No clientId found from stackify.properties file." if @clientId.nil?
253
- warn "No deviceId found from stackify.properties file." if @deviceId.nil?
270
+ info 'No clientId found from stackify.properties file.' if @client_id.nil?
271
+ info 'No deviceId found from stackify.properties file.' if @device_id.nil?
254
272
  end
255
273
  end
256
- # rubocop:enable Metrics/ClassLength
257
274
  end
@@ -1,12 +1,11 @@
1
1
  # frozen_string_literal: true
2
- #
2
+
3
3
  # This class initializes the parameters and variables for the context of Transaction/Span
4
- #
5
4
 
6
- require 'stackify/context/request'
7
- require 'stackify/context/request/socket'
8
- require 'stackify/context/request/url'
9
- require 'stackify/context/response'
5
+ require 'stackify_apm/context/request'
6
+ require 'stackify_apm/context/request/socket'
7
+ require 'stackify_apm/context/request/url'
8
+ require 'stackify_apm/context/response'
10
9
 
11
10
  module StackifyRubyAPM
12
11
  # @api private
File without changes
@@ -25,17 +25,13 @@ module StackifyRubyAPM
25
25
  end
26
26
 
27
27
  attr_reader :protocol, :hostname, :port, :pathname, :search, :hash,
28
- :full
28
+ :full
29
29
 
30
30
  private
31
31
 
32
32
  def build_full_url(req)
33
33
  url = "#{req.scheme}://#{req.host}"
34
-
35
- if req.port != SKIPPED_PORTS[req.scheme]
36
- url += ":#{req.port}"
37
- end
38
-
34
+ url += ":#{req.port}" if req.port != SKIPPED_PORTS[req.scheme]
39
35
  url + req.fullpath
40
36
  end
41
37
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StackifyRubyAPM
4
+ class Context
5
+ # @api private
6
+ class Response
7
+ include NaivelyHashable
8
+
9
+ def initialize(
10
+ status_code,
11
+ headers: {},
12
+ headers_sent: true,
13
+ finished: true
14
+ )
15
+ @status_code = status_code
16
+ headers = make_xstackify_id_header(headers)
17
+ @headers = headers
18
+ @headers_sent = headers_sent
19
+ @finished = finished
20
+ end
21
+
22
+ attr_accessor :status_code, :headers, :headers_sent, :finished
23
+
24
+ def make_xstackify_id_header(headers)
25
+ return unless StackifyRubyAPM.agent.current_transaction && StackifyRubyAPM.agent.current_transaction.id
26
+ transaction_id = StackifyRubyAPM.agent.current_transaction.id
27
+ client_id = StackifyRubyAPM.agent.config.client_id
28
+ device_id = StackifyRubyAPM.agent.config.device_id
29
+ headers['X-StackifyID'] = "V1|#{transaction_id}|C#{client_id}|CD#{device_id}" if client_id && device_id
30
+ end
31
+ end
32
+ end
33
+ end