stackify-ruby-apm 0.9.0 → 1.0.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +18 -0
  3. data/Gemfile.lock +181 -8
  4. data/LICENSE.md +66 -0
  5. data/README.md +5 -3
  6. data/Rakefile +4 -0
  7. data/docker-compose.yml +46 -0
  8. data/lib/stackify/agent.rb +22 -9
  9. data/lib/stackify/config.rb +65 -29
  10. data/lib/stackify/context/response.rb +13 -0
  11. data/lib/stackify/context_builder.rb +0 -2
  12. data/lib/stackify/error.rb +2 -2
  13. data/lib/stackify/error_builder.rb +1 -1
  14. data/lib/stackify/instrumenter.rb +4 -4
  15. data/lib/stackify/logger/logger_high_version.rb +65 -0
  16. data/lib/stackify/logger/logger_lower_version.rb +63 -0
  17. data/lib/stackify/middleware.rb +0 -4
  18. data/lib/stackify/normalizers.rb +0 -1
  19. data/lib/stackify/normalizers/active_record.rb +3 -1
  20. data/lib/stackify/railtie.rb +0 -1
  21. data/lib/stackify/serializers/errors.rb +8 -7
  22. data/lib/stackify/serializers/transactions.rb +3 -2
  23. data/lib/stackify/span.rb +1 -2
  24. data/lib/stackify/span/context.rb +5 -1
  25. data/lib/stackify/spies/action_dispatch.rb +8 -1
  26. data/lib/stackify/spies/httpclient.rb +14 -5
  27. data/lib/stackify/spies/httprb.rb +45 -0
  28. data/lib/stackify/spies/mongo.rb +13 -1
  29. data/lib/stackify/spies/net_http.rb +14 -3
  30. data/lib/stackify/spies/redis.rb +66 -0
  31. data/lib/stackify/spies/sinatra.rb +3 -1
  32. data/lib/stackify/spies/sinatra_activerecord/mysql_adapter.rb +177 -0
  33. data/lib/stackify/spies/sinatra_activerecord/postgresql_adapter.rb +96 -0
  34. data/lib/stackify/spies/sinatra_activerecord/sqlite_adapter.rb +48 -0
  35. data/lib/stackify/spies/tilt.rb +8 -1
  36. data/lib/stackify/stacktrace_builder.rb +4 -3
  37. data/lib/stackify/subscriber.rb +4 -4
  38. data/lib/stackify/trace_logger.rb +6 -23
  39. data/lib/stackify/transaction.rb +10 -1
  40. data/lib/stackify/util.rb +0 -13
  41. data/lib/stackify/version.rb +1 -1
  42. data/lib/stackify/worker.rb +5 -5
  43. data/lib/stackify_ruby_apm.rb +2 -6
  44. data/run-test.sh +75 -0
  45. data/stackify-ruby-apm.gemspec +0 -1
  46. metadata +12 -17
  47. data/lib/stackify/logger.rb +0 -10
@@ -10,13 +10,14 @@ module StackifyRubyAPM
10
10
  # rubocop:disable Metrics/ClassLength
11
11
  # @api private
12
12
  class Config
13
+ include Log
13
14
  DEFAULTS = {
14
15
  config_file: 'config/stackify_apm.yml',
15
16
  environment_name: ENV['RAILS_ENV'] || ENV['RACK_ENV'],
16
17
  instrument: true,
17
18
 
18
19
  log_path: '/usr/local/stackify/stackify-ruby-apm/log/stackify-ruby-apm.log',
19
- log_level: Logger::DEBUG,
20
+ log_level: Logger::INFO,
20
21
 
21
22
  log_trace_path: '/usr/local/stackify/stackify-ruby-apm/log/',
22
23
 
@@ -24,7 +25,13 @@ module StackifyRubyAPM
24
25
  flush_interval: 1, # interval with which transactions should be sent to the APM. Default value: 10 seconds
25
26
 
26
27
  filter_exception_types: [],
27
-
28
+
29
+ tracer_logger: nil,
30
+ logger_byte_size: 50000000,
31
+ filenum_rotate: 20,
32
+ debugger_byte_size: 20000000,
33
+ debugger_filenum_rotate: 3,
34
+ logtime_created: 0,
28
35
  http_status: nil,
29
36
  debug_transactions: false,
30
37
 
@@ -37,7 +44,8 @@ module StackifyRubyAPM
37
44
  disabled_spies: %w[json],
38
45
 
39
46
  view_paths: [],
40
- root_path: Dir.pwd
47
+ root_path: Dir.pwd,
48
+ stackify_properties_file: '/usr/local/stackify/stackify-ruby-apm/stackify.properties'
41
49
  }.freeze
42
50
 
43
51
  ENV_TO_KEY = {
@@ -67,12 +75,14 @@ module StackifyRubyAPM
67
75
  yield self if block_given?
68
76
 
69
77
  build_logger if logger.nil? || log_path
78
+ load_stackify_props
70
79
  end
71
80
 
72
81
  attr_accessor :config_file
73
82
  attr_accessor :environment_name
74
83
  attr_accessor :instrument
75
84
  attr_accessor :enabled_environments
85
+ attr_accessor :stackify_properties_file
76
86
 
77
87
  attr_accessor :application_name
78
88
  attr_accessor :hostname
@@ -80,6 +90,13 @@ module StackifyRubyAPM
80
90
  attr_accessor :log_path
81
91
  attr_accessor :log_level
82
92
  attr_accessor :logger
93
+ attr_accessor :logger_byte_size
94
+ attr_accessor :filenum_rotate
95
+ attr_accessor :debugger_byte_size
96
+ attr_accessor :debugger_filenum_rotate
97
+
98
+ attr_accessor :tracer_logger
99
+ attr_accessor :logtime_created
83
100
 
84
101
  attr_accessor :log_trace_path
85
102
  attr_accessor :source_lines_error_app_frames
@@ -101,6 +118,9 @@ module StackifyRubyAPM
101
118
  attr_accessor :root_path
102
119
  attr_accessor :http_status
103
120
 
121
+ attr_reader :clientId
122
+ attr_reader :deviceId
123
+
104
124
  def app=(app)
105
125
  case app_type?(app)
106
126
  when :rails
@@ -118,42 +138,36 @@ module StackifyRubyAPM
118
138
  nil
119
139
  end
120
140
 
121
- # rubocop:disable Metrics/MethodLength
141
+ # rubocop:disable Metrics/MethodLength
142
+ # available spies to use when framework is rails
122
143
  def available_spies
123
144
  %w[
124
145
  action_dispatch
125
146
  mongo
126
147
  net_http
127
148
  httpclient
128
- sinatra
149
+ redis
129
150
  tilt
151
+ httprb
130
152
  ]
131
153
  end
132
154
  # rubocop:enable Metrics/MethodLength
133
155
 
134
156
  def enabled_spies
135
- available_spies - disabled_spies
136
- end
137
-
138
- def check_lastlog_needs_new(path)
139
- latest_file = Dir.glob("#{path}*").grep(/([#])\w+/).max_by {|f| File.mtime(f)}
140
- new_flagger = false
141
-
142
- if latest_file.nil?
143
- new_flagger = true
157
+ # check if the framework is rails or sinatra
158
+ sinatra_spies = %w[
159
+ sinatra
160
+ sinatra_activerecord/mysql_adapter
161
+ sinatra_activerecord/postgresql_adapter
162
+ sinatra_activerecord/sqlite_adapter
163
+ ]
164
+ if (defined?(::Sinatra::Base))
165
+ new_available_spies = available_spies + sinatra_spies
144
166
  else
145
- current_kb_size = ((File.size(latest_file)) / 1000).to_f
146
-
147
- #50000KB = 50MB
148
- if current_kb_size > 50000.0
149
- new_flagger = true
150
- end
151
- end
152
- result = {
153
- 'latest_file' => latest_file,
154
- 'new_flagger' => new_flagger
155
- }
156
-
167
+ new_available_spies = available_spies
168
+ end
169
+
170
+ new_available_spies - disabled_spies
157
171
  end
158
172
 
159
173
  private
@@ -207,15 +221,37 @@ module StackifyRubyAPM
207
221
  end
208
222
 
209
223
  def build_logger
210
- logger = Logger.new(log_path == '-' ? $stdout : log_path)
224
+ debuger_logpath = log_path == '-' ? $stdout : log_path
225
+ logger = StackifyLogger.new(debuger_logpath, debugger_filenum_rotate, debugger_byte_size)
211
226
  logger.level = log_level
212
-
213
- self.logger = logger
227
+ self.logger = logger
214
228
  end
215
229
 
216
230
  def format_name(str)
217
231
  str.gsub('::', '_')
218
232
  end
233
+
234
+ def load_stackify_props
235
+ @clientId = nil
236
+ @deviceId = nil
237
+ begin
238
+ warn "Reading the stackify.properties file"
239
+ IO.foreach(@stackify_properties_file) { |line|
240
+ case line.downcase
241
+ when /clientid=\d+/
242
+ @clientId = line.split('=')[1].strip
243
+ when /deviceid=\d+/
244
+ @deviceId = line.split('=')[1].strip
245
+ end
246
+ }
247
+ warn "stackify.properties: clientId=#{@clientId}, deviceId=#{@deviceId}"
248
+ rescue StandardError => e
249
+ warn "Error occured while reading the stackify.properties file."
250
+ warn e.inspect
251
+ 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?
254
+ end
219
255
  end
220
256
  # rubocop:enable Metrics/ClassLength
221
257
  end
@@ -13,12 +13,25 @@ module StackifyRubyAPM
13
13
  finished: true
14
14
  )
15
15
  @status_code = status_code
16
+ headers = self.make_xstackifyId_header(headers)
16
17
  @headers = headers
17
18
  @headers_sent = headers_sent
18
19
  @finished = finished
19
20
  end
20
21
 
21
22
  attr_accessor :status_code, :headers, :headers_sent, :finished
23
+
24
+ def make_xstackifyId_header(headers)
25
+ if (StackifyRubyAPM.agent.current_transaction &&
26
+ StackifyRubyAPM.agent.current_transaction.id)
27
+ transaction_id = StackifyRubyAPM.agent.current_transaction.id
28
+ clientId = StackifyRubyAPM.agent.config.clientId
29
+ deviceId = StackifyRubyAPM.agent.config.deviceId
30
+ if clientId && deviceId
31
+ headers['X-StackifyID'] = "V1|#{transaction_id}|C#{clientId}|CD#{deviceId}"
32
+ end
33
+ end
34
+ end
22
35
  end
23
36
  end
24
37
  end
@@ -12,7 +12,6 @@ module StackifyRubyAPM
12
12
  # Creates context
13
13
  #
14
14
  def build(rack_env)
15
- # puts "@stackify_ruby [lib/context_builder.rb] Loading ContextBuilder build(rack_env)"
16
15
  context = Context.new
17
16
  apply_to_request(context, rack_env)
18
17
  context
@@ -25,7 +24,6 @@ module StackifyRubyAPM
25
24
  # Request format and values are assigned to context
26
25
  #
27
26
  def apply_to_request(context, rack_env)
28
- # puts "@stackify_ruby [lib/context_builder.rb] Loading ContextBuilder apply_to_request(context, rack_env)"
29
27
  req = rails_req?(rack_env) ? rack_env : Rack::Request.new(rack_env)
30
28
  context.request = Context::Request.new unless context.request
31
29
  request = context.request
@@ -12,7 +12,7 @@ module StackifyRubyAPM
12
12
  @id = SecureRandom.uuid
13
13
  @culprit = culprit
14
14
 
15
- @timestamp = Util.micros
15
+ @timestamp = (Time.now).to_f * 1000
16
16
  @context = Context.new
17
17
 
18
18
  @transaction_id = nil
@@ -21,4 +21,4 @@ module StackifyRubyAPM
21
21
  attr_accessor :id, :culprit, :exception, :log, :transaction_id, :context
22
22
  attr_reader :timestamp
23
23
  end
24
- end
24
+ end
@@ -43,7 +43,7 @@ module StackifyRubyAPM
43
43
 
44
44
  def add_stacktrace(error, kind, backtrace)
45
45
  stacktrace =
46
- @agent.stacktrace_builder.build(backtrace, type: :error)
46
+ @agent.stacktrace_builder.build(backtrace, :error)
47
47
  return unless stacktrace
48
48
 
49
49
  case kind
@@ -18,7 +18,7 @@ module StackifyRubyAPM
18
18
  end
19
19
 
20
20
  def current
21
- # puts '@stackify_ruby [Instrumenter] [lib/instrumenter.rb] TransactionInfo.current()'
21
+ # puts '[Instrumenter] [lib/instrumenter.rb] TransactionInfo.current()'
22
22
  Thread.current[KEY]
23
23
  end
24
24
 
@@ -28,7 +28,7 @@ module StackifyRubyAPM
28
28
  end
29
29
 
30
30
  def initialize(agent)
31
- debug '@stackify_ruby [Instrumenter] [lib/instrumenter.rb] initialize()'
31
+ debug '[Instrumenter] initialize()'
32
32
  @agent = agent
33
33
  @config = agent.config
34
34
 
@@ -61,7 +61,7 @@ module StackifyRubyAPM
61
61
  # Creates a new transaction or return the currently running
62
62
  #
63
63
  def transaction(*args)
64
- debug "Instrumenter function transaction(*args)"
64
+ debug "[Instrumenter] transaction(*args)"
65
65
  unless config.instrument
66
66
  yield if block_given?
67
67
  return
@@ -103,7 +103,7 @@ module StackifyRubyAPM
103
103
  # Once the transaction is submitted it will be stored temporarily in queue
104
104
  #
105
105
  def submit_transaction(transaction)
106
- debug '@stackify_ruby [Instrumenter] [lib/instrumenter.rb] submit_transaction(transaction):'
106
+ debug '[Instrumenter] submit_transaction(transaction):'
107
107
  debug transaction.inspect
108
108
  agent.enqueue_transaction transaction
109
109
  return unless config.debug_transactions
@@ -0,0 +1,65 @@
1
+ # Starts the Monkey patch for the generated log by not including the header
2
+ #
3
+ # @param file
4
+ # @removed the header of the generated log
5
+ module StackifyRubyAPM
6
+
7
+ class StackifyLogger < Logger
8
+
9
+ def initialize(logdev, shift_age = 0, shift_size = 1048576, level: DEBUG,
10
+ progname: nil, formatter: nil, datetime_format: nil,
11
+ shift_period_suffix: '%Y%m%d')
12
+ super(nil) # this prevents it from initializing a LogDevice
13
+ @logdev = nil
14
+ if logdev
15
+ new_logdev = logdev
16
+ if (logdev.instance_of? String)
17
+ temp_filename = logdev.gsub(".log", "")
18
+ new_logdev = temp_filename + "-1.log"
19
+ end
20
+
21
+ @logdev = LogDevice.new(new_logdev, :shift_age => shift_age,
22
+ :shift_size => shift_size,
23
+ :shift_period_suffix => shift_period_suffix)
24
+ end
25
+ end
26
+ class LogDevice < Logger::LogDevice
27
+ def add_log_header(file)
28
+ end
29
+ # This is the monkeypatch of core Logger method where reformats when shifting log age the filename when creating the file log.
30
+ def shift_log_age
31
+ temp_filename = @filename.gsub(/\-([0-9]).log/,"")
32
+ (@shift_age-1).downto(2) do |i|
33
+ if FileTest.exist?("#{temp_filename}-#{i}.log")
34
+ File.rename("#{temp_filename}-#{i}.log", "#{temp_filename}-#{i+1}.log")
35
+ end
36
+ end
37
+ @dev.close rescue nil
38
+ File.rename("#{temp_filename}-1.log", "#{temp_filename}-2.log")
39
+ @dev = create_logfile(@filename)
40
+
41
+ return true
42
+ end
43
+ # This is the monkeypatch of core Logger method where reformats the file name when creating the file log.
44
+ def shift_log_period(period_end)
45
+ suffix = period_end.strftime(@shift_period_suffix)
46
+ age_file = "#{@filename}.#{suffix}"
47
+ if FileTest.exist?(age_file)
48
+ # try to avoid filename crash caused by Timestamp change.
49
+ idx = 1
50
+ # .99 can be overridden; avoid too much file search with 'loop do'
51
+ while idx < 100
52
+ idx += 1
53
+ age_file = "#{@filename}-#{idx}.#{suffix}"
54
+ break unless FileTest.exist?(age_file)
55
+ end
56
+ end
57
+ @dev.close rescue nil
58
+ File.rename("#{@filename}", age_file)
59
+ @dev = create_logfile(@filename)
60
+
61
+ return true
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,63 @@
1
+ # Starts the Monkey patch for the generated log by not including the header
2
+ #
3
+ # @param file
4
+ # @removed the header of the generated log
5
+ module StackifyRubyAPM
6
+ class StackifyLogger < Logger
7
+
8
+ def initialize(logdev, shift_age = 0, shift_size = 1048576)
9
+ super(nil) # this prevents it from initializing a LogDevice
10
+ @logdev = nil
11
+ if logdev
12
+ new_logdev = logdev
13
+ if (logdev.instance_of? String)
14
+ temp_filename = logdev.gsub(".log", "")
15
+ new_logdev = temp_filename + "-1.log"
16
+ end
17
+
18
+ @logdev = LogDevice.new(new_logdev, :shift_age => shift_age,
19
+ :shift_size => shift_size)
20
+ end
21
+ end
22
+
23
+ class LogDevice < Logger::LogDevice
24
+
25
+ def add_log_header(file)
26
+ end
27
+ # This is the monkeypatch of core Logger method where reformats when shifting log age the filename when creating the file log.
28
+ def shift_log_age
29
+ temp_filename = @filename.gsub(/\-([0-9]).log/,"")
30
+ (@shift_age-1).downto(2) do |i|
31
+ if FileTest.exist?("#{temp_filename}-#{i}.log")
32
+ File.rename("#{temp_filename}-#{i}.log", "#{temp_filename}-#{i+1}.log")
33
+ end
34
+ end
35
+ @dev.close rescue nil
36
+ File.rename("#{temp_filename}-1.log", "#{temp_filename}-2.log")
37
+ @dev = create_logfile(@filename)
38
+
39
+ return true
40
+ end
41
+ # This is the monkeypatch of core Logger method where reformats the file name when creating the file log.
42
+ def shift_log_period(period_end)
43
+ suffix = period_end.strftime(@shift_period_suffix)
44
+ age_file = "#{@filename}.#{suffix}"
45
+ if FileTest.exist?(age_file)
46
+ # try to avoid filename crash caused by Timestamp change.
47
+ idx = 1
48
+ # .99 can be overridden; avoid too much file search with 'loop do'
49
+ while idx < 100
50
+ idx += 1
51
+ age_file = "#{@filename}-#{idx}.#{suffix}"
52
+ break unless FileTest.exist?(age_file)
53
+ end
54
+ end
55
+ @dev.close rescue nil
56
+ File.rename("#{@filename}", age_file)
57
+ @dev = create_logfile(@filename)
58
+
59
+ return true
60
+ end
61
+ end
62
+ end
63
+ end
@@ -22,7 +22,6 @@ module StackifyRubyAPM
22
22
 
23
23
  class Middleware
24
24
  def initialize(app)
25
- # puts "@stackify_ruby [Middleware] [lib/middleware.rb] initialize(app)"
26
25
  @app = app
27
26
  end
28
27
 
@@ -38,13 +37,11 @@ module StackifyRubyAPM
38
37
  end
39
38
 
40
39
  resp = @app.call env
41
- # puts "@stackify_ruby [Middleware] [lib/middleware.rb] Loads call(env) in middleware module"
42
40
 
43
41
  submit_transaction(transaction, *resp) if transaction
44
42
  rescue InternalError
45
43
  raise # Don't report StackifyRubyAPM errors
46
44
  rescue ::Exception => e
47
- #puts "@stackify_ruby [Middleware] [lib/middleware.rb] Error Message: #{e.message}"
48
45
  transaction.submit('500', status: 500) if transaction
49
46
  raise
50
47
  ensure
@@ -63,7 +60,6 @@ module StackifyRubyAPM
63
60
  # Start of transaction building with params: name, type, context
64
61
  #
65
62
  def build_transaction(env)
66
- # puts "@stackify_ruby [Middleware] [lib/middleware.rb] middleware build transaction env"
67
63
  StackifyRubyAPM.transaction 'Rack', 'request', context: StackifyRubyAPM.build_context(env)
68
64
  end
69
65
 
@@ -23,7 +23,6 @@ module StackifyRubyAPM # :nodoc:
23
23
  end
24
24
 
25
25
  def self.build(config)
26
- # puts '@stackify_ruby [Normalizers] [lib/normalizer.rb] self.build(config)'
27
26
  normalizers = @registered.each_with_object({}) do |(name, klass), built|
28
27
  built[name] = klass.new(config)
29
28
  end