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.
- checksums.yaml +4 -4
- data/Gemfile +18 -0
- data/Gemfile.lock +181 -8
- data/LICENSE.md +66 -0
- data/README.md +5 -3
- data/Rakefile +4 -0
- data/docker-compose.yml +46 -0
- data/lib/stackify/agent.rb +22 -9
- data/lib/stackify/config.rb +65 -29
- data/lib/stackify/context/response.rb +13 -0
- data/lib/stackify/context_builder.rb +0 -2
- data/lib/stackify/error.rb +2 -2
- data/lib/stackify/error_builder.rb +1 -1
- data/lib/stackify/instrumenter.rb +4 -4
- data/lib/stackify/logger/logger_high_version.rb +65 -0
- data/lib/stackify/logger/logger_lower_version.rb +63 -0
- data/lib/stackify/middleware.rb +0 -4
- data/lib/stackify/normalizers.rb +0 -1
- data/lib/stackify/normalizers/active_record.rb +3 -1
- data/lib/stackify/railtie.rb +0 -1
- data/lib/stackify/serializers/errors.rb +8 -7
- data/lib/stackify/serializers/transactions.rb +3 -2
- data/lib/stackify/span.rb +1 -2
- data/lib/stackify/span/context.rb +5 -1
- data/lib/stackify/spies/action_dispatch.rb +8 -1
- data/lib/stackify/spies/httpclient.rb +14 -5
- data/lib/stackify/spies/httprb.rb +45 -0
- data/lib/stackify/spies/mongo.rb +13 -1
- data/lib/stackify/spies/net_http.rb +14 -3
- data/lib/stackify/spies/redis.rb +66 -0
- data/lib/stackify/spies/sinatra.rb +3 -1
- data/lib/stackify/spies/sinatra_activerecord/mysql_adapter.rb +177 -0
- data/lib/stackify/spies/sinatra_activerecord/postgresql_adapter.rb +96 -0
- data/lib/stackify/spies/sinatra_activerecord/sqlite_adapter.rb +48 -0
- data/lib/stackify/spies/tilt.rb +8 -1
- data/lib/stackify/stacktrace_builder.rb +4 -3
- data/lib/stackify/subscriber.rb +4 -4
- data/lib/stackify/trace_logger.rb +6 -23
- data/lib/stackify/transaction.rb +10 -1
- data/lib/stackify/util.rb +0 -13
- data/lib/stackify/version.rb +1 -1
- data/lib/stackify/worker.rb +5 -5
- data/lib/stackify_ruby_apm.rb +2 -6
- data/run-test.sh +75 -0
- data/stackify-ruby-apm.gemspec +0 -1
- metadata +12 -17
- data/lib/stackify/logger.rb +0 -10
data/lib/stackify/config.rb
CHANGED
@@ -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::
|
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
|
-
|
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
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
if
|
143
|
-
|
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
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
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
|
-
|
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
|
data/lib/stackify/error.rb
CHANGED
@@ -12,7 +12,7 @@ module StackifyRubyAPM
|
|
12
12
|
@id = SecureRandom.uuid
|
13
13
|
@culprit = culprit
|
14
14
|
|
15
|
-
@timestamp =
|
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
|
@@ -18,7 +18,7 @@ module StackifyRubyAPM
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def current
|
21
|
-
# puts '
|
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 '
|
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
|
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 '
|
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
|
data/lib/stackify/middleware.rb
CHANGED
@@ -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
|
|
data/lib/stackify/normalizers.rb
CHANGED
@@ -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
|