stackify-ruby-apm 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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