stackify-ruby-apm 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -37,11 +37,13 @@ module StackifyRubyAPM
37
37
  "oracle"
38
38
  elsif driver.include? "db2"
39
39
  "db2"
40
+ elsif driver.include? "sqlite"
41
+ "sqlite"
40
42
  end
41
43
  end
42
44
 
43
45
  def query_variables(payload)
44
- debug "@stackify_ruby [SqlNormalizer] [normalizers/active_record.rb] query_variables payload:"
46
+ debug "[SqlNormalizer] query_variables payload:"
45
47
  debug payload.inspect
46
48
 
47
49
  if payload[:type_casted_binds]
@@ -24,7 +24,6 @@ module StackifyRubyAPM
24
24
  # This will overwrite the default values of the config based from stackify_apm.yml initialized
25
25
 
26
26
  initializer 'stackify_apm.initialize' do |app|
27
- # puts '@stackify_ruby [Railtie] [lib/railtie.rb] initializer'
28
27
  config = app.config.stackify.merge(app: app).tap do |c|
29
28
  # Prepend Rails.root to log_path if present
30
29
  if c.log_path && !c.log_path.start_with?('/')
@@ -10,14 +10,15 @@ module StackifyRubyAPM
10
10
  def build(error)
11
11
 
12
12
  if (exception = error.exception)
13
+ current_timestamp = error.timestamp
13
14
 
14
- base = {
15
- CaughtBy: exception.module,
16
- Exception: exception.type,
17
- Message: exception.message,
18
- Timestamp: micros_to_time(error.timestamp).utc.iso8601(3),
19
- Frames: exception.stacktrace.to_a,
20
- }
15
+ base = {
16
+ CaughtBy: exception.module,
17
+ Exception: exception.type,
18
+ Message: exception.message,
19
+ Timestamp: "#{current_timestamp.round}",
20
+ Frames: exception.stacktrace.to_a,
21
+ }
21
22
 
22
23
  end
23
24
 
@@ -17,7 +17,9 @@ module StackifyRubyAPM
17
17
  call: transaction.name,
18
18
  reqBegin: transaction.timestamp,
19
19
  reqEnd: transaction.duration,
20
- props: RootInfo.build(config, transaction)
20
+ props: RootInfo.build(config, transaction),
21
+ exceptions: transaction.exceptions,
22
+ stacks: []
21
23
  }
22
24
 
23
25
  # serialize all the spans
@@ -27,7 +29,6 @@ module StackifyRubyAPM
27
29
  end
28
30
 
29
31
  add_children_spans(root_span_json, children_spans_json)
30
-
31
32
  root_span_json
32
33
  end
33
34
 
data/lib/stackify/span.rb CHANGED
@@ -27,7 +27,6 @@ module StackifyRubyAPM
27
27
  @parent_id = parent_id
28
28
  @context = context
29
29
  @http_status = http_status
30
-
31
30
  @stacktrace = nil
32
31
  @original_backtrace = nil
33
32
  end
@@ -68,4 +67,4 @@ module StackifyRubyAPM
68
67
  private
69
68
 
70
69
  end
71
- end
70
+ end
@@ -20,7 +20,11 @@ module StackifyRubyAPM
20
20
  :METHOD,
21
21
  :URL,
22
22
  :STATUS,
23
- :PROVIDER
23
+ :PROVIDER,
24
+ :OPERATION,
25
+ :CACHEKEY,
26
+ :CACHENAME,
27
+ :THREAD_ID
24
28
  end
25
29
  end
26
30
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
- # This module monkey patches usually in Exceptions class
2
+ #
3
+ # Monkey patch for the ActionDispatch::ShowExceptions class which handles exception events.
4
+ #
5
+
3
6
  module StackifyRubyAPM
4
7
  # @api private
5
8
  module Spies
@@ -10,6 +13,8 @@ module StackifyRubyAPM
10
13
  alias render_exception_without_apm render_exception
11
14
 
12
15
  def render_exception(env, exception)
16
+ # Creates exception log report
17
+ #
13
18
  StackifyRubyAPM.report(exception)
14
19
  render_exception_without_apm env, exception
15
20
  end
@@ -17,6 +22,8 @@ module StackifyRubyAPM
17
22
  end
18
23
  end
19
24
 
25
+ # Registers ActionDispatch spy, go to: /stackify/spies.rb
26
+ #
20
27
  register(
21
28
  'ActionDispatch::ShowExceptions',
22
29
  'action_dispatch/show_exception',
@@ -1,14 +1,11 @@
1
- require 'stackify/log'
2
- require 'stackify/logger'
1
+ # Monkey patch for the HTTPClient class for sending & receiving HTTP responses.
2
+ #
3
3
 
4
4
  module StackifyRubyAPM
5
5
  # @api private
6
6
  module Spies
7
7
  # @api private
8
8
  class HTTPClientSpy
9
- include Log
10
-
11
- # This class monkeypatches the HTTPClient class with request method.
12
9
  def install
13
10
  HTTPClient.class_eval do
14
11
  alias request_without_apm request
@@ -18,12 +15,20 @@ module StackifyRubyAPM
18
15
  unless StackifyRubyAPM.current_transaction
19
16
  return request_without_apm(method, uri, *args, &block)
20
17
  end
18
+
19
+ # Data configuration
20
+ #
21
21
  method = method.upcase
22
22
  uri = uri.strip
23
23
  name = "#{method} #{uri}"
24
24
  type = "ext.httpclient.#{method}"
25
25
 
26
+ # Submits HTTP request
27
+ #
26
28
  req = request_without_apm(method, uri, *args, &block)
29
+
30
+ # Builds span context
31
+ #
27
32
  ctx = Span::Context.new(
28
33
  CATEGORY: 'Web External',
29
34
  SUBCATEGORY: 'Execute',
@@ -32,6 +37,8 @@ module StackifyRubyAPM
32
37
  METHOD: method
33
38
  )
34
39
 
40
+ # Creates new span from HTTP result
41
+ #
35
42
  StackifyRubyAPM.span name, type, context: ctx do
36
43
  req
37
44
  end
@@ -42,6 +49,8 @@ module StackifyRubyAPM
42
49
  # rubocop:enable Metrics/MethodLength
43
50
  end
44
51
 
52
+ # Registers HTTPClient spy, go to: /stackify/spies.rb
53
+ #
45
54
  register 'HTTPClient', 'httpclient', HTTPClientSpy.new
46
55
  end
47
56
  end
@@ -0,0 +1,45 @@
1
+ #
2
+ # This class monkey patch the http.rb gem with the request method.
3
+ #
4
+ module StackifyRubyAPM
5
+ # @api private
6
+ module Spies
7
+ # @api private
8
+ class HTTPRbSpy
9
+
10
+ def install
11
+ HTTP::Client.class_eval do
12
+ alias request_without_apm request
13
+
14
+ # Make HTTP request
15
+ def request(verb, uri, opts = {})
16
+ unless StackifyRubyAPM.current_transaction
17
+ return request_without_apm(verb, uri, opts = {})
18
+ end
19
+
20
+ method = verb.upcase
21
+ uri = uri.strip
22
+ name = "#{method} #{uri}"
23
+ type = "ext.httprb.#{method}"
24
+
25
+ req = request_without_apm(verb, uri, opts = {})
26
+ ctx = Span::Context.new(
27
+ CATEGORY: 'Web External',
28
+ SUBCATEGORY: 'Execute',
29
+ URL: uri,
30
+ STATUS: req.code,
31
+ METHOD: method
32
+ )
33
+
34
+ StackifyRubyAPM.span name, type, context: ctx do
35
+ req
36
+ end
37
+ end
38
+ end
39
+ end
40
+ # rubocop:enable Metrics/MethodLength
41
+ end
42
+
43
+ register 'HTTP::Client', 'http/client', HTTPRbSpy.new
44
+ end
45
+ end
@@ -1,8 +1,11 @@
1
+ # Monkey patch for the Mongo::Monitoring::Global module in mongoDB(http://api.mongodb.com/ruby/current/Mongo/Monitoring/Global.html)
2
+ # -> Allows subscribing to events for all Mongo clients.
3
+ #
4
+
1
5
  module StackifyRubyAPM
2
6
  # @api private
3
7
  module Spies
4
8
  # @api private
5
- # This class monkeypatch the Mongo::Monitoring::Global module in mongoDB(http://api.mongodb.com/ruby/current/Mongo/Monitoring/Global.html)
6
9
  class MongoSpy
7
10
  def install
8
11
  ::Mongo::Monitoring::Global.subscribe(
@@ -36,6 +39,8 @@ module StackifyRubyAPM
36
39
  def push_event(event)
37
40
  return unless StackifyRubyAPM.current_transaction
38
41
 
42
+ # Fetches collection name from Mongo event
43
+ #
39
44
  document = event.command.find
40
45
  col = nil
41
46
  if document
@@ -44,10 +49,15 @@ module StackifyRubyAPM
44
49
  end
45
50
  end
46
51
 
52
+ # Builds span context
53
+ #
47
54
  ctx = Span::Context.new(
48
55
  CATEGORY: 'MongoDB',
49
56
  MONGODB_COLLECTION: col,
50
57
  )
58
+
59
+ # Creates new span from Mongo event
60
+ #
51
61
  span = StackifyRubyAPM.span(event.command_name, TYPE, context: ctx)
52
62
  @events[event.operation_id] = span
53
63
  end
@@ -61,6 +71,8 @@ module StackifyRubyAPM
61
71
  end
62
72
  end
63
73
 
74
+ # Registers Mongo spy, go to: /stackify/spies.rb
75
+ #
64
76
  register 'Mongo', 'mongo', MongoSpy.new
65
77
  end
66
78
  end
@@ -1,9 +1,11 @@
1
+ # Monkey patch for the Net::HTTP class for sending & receiving HTTP responses.
2
+ #
3
+
1
4
  module StackifyRubyAPM
2
5
  # @api private
3
6
  module Spies
4
7
  # @api private
5
8
  class NetHTTPSpy
6
- # This class monkeypatch the Net::HTTP class with request method.
7
9
  def install
8
10
  Net::HTTP.class_eval do
9
11
  alias request_without_apm request
@@ -14,6 +16,8 @@ module StackifyRubyAPM
14
16
  request_without_apm(req, body, &block)
15
17
  end
16
18
 
19
+ # Data configuration
20
+ #
17
21
  host, = req['host'] && req['host'].split(':')
18
22
  method = req.method
19
23
 
@@ -22,16 +26,22 @@ module StackifyRubyAPM
22
26
  name = "#{method} #{host}"
23
27
  type = "ext.net_http.#{method}"
24
28
 
29
+ # Submits HTTP request
30
+ #
25
31
  result = request_without_apm(req, body, &block)
26
32
 
33
+ # Builds span context
34
+ #
27
35
  ctx = Span::Context.new(
28
36
  CATEGORY: 'Web External',
29
37
  SUBCATEGORY: 'Execute',
30
- URL: req.uri,
38
+ URL: req.uri.nil? ? host : req.uri,
31
39
  STATUS: result.code.to_i,
32
40
  METHOD: method
33
41
  )
34
42
 
43
+ # Creates new span from HTTP result
44
+ #
35
45
  StackifyRubyAPM.span name, type, context: ctx do
36
46
  return result
37
47
  end
@@ -41,7 +51,8 @@ module StackifyRubyAPM
41
51
  # rubocop:enable Metrics/MethodLength
42
52
  end
43
53
 
44
-
54
+ # Registers Net::HTTP spy, go to: /stackify/spies.rb
55
+ #
45
56
  register 'Net::HTTP', 'net/http', NetHTTPSpy.new
46
57
  end
47
58
  end
@@ -0,0 +1,66 @@
1
+ # Monkey patch for Redis.
2
+ #
3
+
4
+ module StackifyRubyAPM
5
+ # @api private
6
+ module Spies
7
+ # @api private
8
+ class RedisSpy
9
+
10
+ def install
11
+ ::Redis::Client.class_eval do
12
+ alias call_without_apm call
13
+
14
+ def call(command, &block)
15
+ name = command[0].upcase.to_s
16
+ type = 'db.redis'
17
+ redis_details = command[1].is_a?(String) ? command[1].split(":") : []
18
+ redis_nspace = !redis_details.blank? ? redis_details[0] : ""
19
+ redis_key = ""
20
+
21
+ # Checks CACHEKEY value
22
+ if !redis_details.blank? && redis_details[1]
23
+ # Initially sets the CACHEKEY value
24
+ args = redis_details[1].split("/")
25
+ redis_key = args[0]
26
+
27
+ # If command has passed __method__, it will be included in the CACHEKEY value
28
+ # Possible formats:
29
+ # `<namespace:key/method_name/expires_in=300/ttl=60/>`
30
+ # `<namespace:key/expires_in=300/ttl=60/>`
31
+ if args.length > 1 && !args[1].include?("=")
32
+ redis_key = args[0..1].join("/")
33
+ end
34
+ end
35
+
36
+ return call_without_apm(command, &block) if command[0] == :auth
37
+
38
+ context = {
39
+ PROVIDER: 'Redis',
40
+ CATEGORY: 'Cache',
41
+ SUBCATEGORY: 'Execute',
42
+ COMPONENT_CATEGORY: 'Cache',
43
+ COMPONENT_DETAIL: 'Execute',
44
+ THREAD_ID: Thread.current.object_id,
45
+ OPERATION: name
46
+ }.tap do |hash|
47
+ hash[:CACHEKEY] = redis_key unless redis_key.empty?
48
+ hash[:CACHENAME] = redis_nspace unless redis_nspace.empty?
49
+ end
50
+
51
+ ctx = Span::Context.new(context)
52
+
53
+ StackifyRubyAPM.span name, type, context: ctx do
54
+ call_without_apm(command, &block)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ # Registers Redis spy, go to: /stackify/spies.rb
63
+ #
64
+ register 'Redis', 'redis', RedisSpy.new
65
+ end
66
+ end
@@ -25,7 +25,7 @@ module StackifyRubyAPM
25
25
  end
26
26
  end
27
27
 
28
- # Tilt engine templating
28
+ # Tilt engine template
29
29
  #
30
30
  def compile_template(engine, data, opts, *args, &block)
31
31
  opts[:__stackify_apm_template_name] =
@@ -45,6 +45,8 @@ module StackifyRubyAPM
45
45
  #
46
46
  register 'Sinatra::Base', 'sinatra/base', SinatraSpy.new
47
47
 
48
+ # Tilt template helper & span creator
49
+ #
48
50
  require 'stackify/spies/tilt'
49
51
  end
50
52
  end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+ # Spies for active record when sinatra framework is used and active_record is being extended. (mysql adapter)
3
+ #
4
+ module StackifyRubyAPM
5
+ # @api private
6
+ module Spies
7
+ # @api private
8
+ class MysqlAdapterSpy
9
+ TYPE = 'db.sinatra_active_record.sql'.freeze
10
+ if ActiveRecord::VERSION::MAJOR.to_i >= 5
11
+ # rubocop:disable Metrics/MethodLength
12
+ def install
13
+ ActiveRecord::ConnectionAdapters::MySQL::DatabaseStatements.class_eval do
14
+ alias exec_query_without_apm exec_query
15
+ alias exec_delete_without_apm exec_delete
16
+ alias exec_update_without_apm exec_update
17
+
18
+ def exec_update(sql, name = nil, binds = [])
19
+ result = nil
20
+ unless StackifyRubyAPM.current_transaction
21
+ exec_update_without_apm(sql, name, binds)
22
+ end
23
+
24
+ ctx = Span::Context.new(
25
+ CATEGORY: 'Database',
26
+ SUBCATEGORY: 'Execute',
27
+ COMPONENT_CATEGORY: 'DB Query',
28
+ COMPONENT_DETAIL: 'Execute SQL Query',
29
+ SQL: sql,
30
+ PROVIDER: "mysql"
31
+ )
32
+ result = exec_update_without_apm(sql, name, binds)
33
+ StackifyRubyAPM.span name, TYPE, context: ctx do
34
+ return result
35
+ end
36
+ end
37
+
38
+ def exec_delete(sql, name = nil, binds = [])
39
+ result = nil
40
+ unless StackifyRubyAPM.current_transaction
41
+ exec_delete_without_apm(sql, name, binds)
42
+ end
43
+
44
+ ctx = Span::Context.new(
45
+ CATEGORY: 'Database',
46
+ SUBCATEGORY: 'Execute',
47
+ COMPONENT_CATEGORY: 'DB Query',
48
+ COMPONENT_DETAIL: 'Execute SQL Query',
49
+ SQL: sql,
50
+ PROVIDER: "mysql"
51
+ )
52
+
53
+ result = exec_delete_without_apm(sql, name, binds)
54
+ StackifyRubyAPM.span name, TYPE, context: ctx do
55
+ return result
56
+ end
57
+ end
58
+
59
+ def exec_query(sql, name = "SQL", binds = [], prepare: false)
60
+ result = nil
61
+ unless StackifyRubyAPM.current_transaction
62
+ exec_query_without_apm(sql, name, binds)
63
+ end
64
+
65
+ ctx = Span::Context.new(
66
+ CATEGORY: 'Database',
67
+ SUBCATEGORY: 'Execute',
68
+ COMPONENT_CATEGORY: 'DB Query',
69
+ COMPONENT_DETAIL: 'Execute SQL Query',
70
+ SQL: sql,
71
+ PROVIDER: "mysql"
72
+ )
73
+
74
+ result = exec_query_without_apm(sql, name, binds)
75
+ StackifyRubyAPM.span name, TYPE, context: ctx do
76
+ return result
77
+ end
78
+ end
79
+ end
80
+ end
81
+ # rubocop:enable Metrics/MethodLength
82
+ else
83
+ # rubocop:disable Metrics/MethodLength
84
+ def install
85
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
86
+ alias exec_query_without_apm exec_query
87
+ alias exec_delete_without_apm exec_delete
88
+ alias exec_update_without_apm exec_update
89
+ alias exec_insert_without_apm exec_insert
90
+
91
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
92
+ result = nil
93
+ unless StackifyRubyAPM.current_transaction
94
+ exec_insert_without_apm(sql, name, binds, pk = nil, sequence_name = nil)
95
+ end
96
+ ctx = Span::Context.new(
97
+ CATEGORY: 'Database',
98
+ SUBCATEGORY: 'Execute',
99
+ COMPONENT_CATEGORY: 'DB Query',
100
+ COMPONENT_DETAIL: 'Execute SQL Query',
101
+ SQL: sql,
102
+ PROVIDER: "mysql"
103
+ )
104
+ result = exec_insert_without_apm(sql, name, binds, pk = nil, sequence_name = nil)
105
+ StackifyRubyAPM.span name, TYPE, context: ctx do
106
+ return result
107
+ end
108
+ end
109
+
110
+ def exec_update(sql, name, binds)
111
+ result = nil
112
+ unless StackifyRubyAPM.current_transaction
113
+ exec_update_without_apm(sql, name, binds)
114
+ end
115
+ ctx = Span::Context.new(
116
+ CATEGORY: 'Database',
117
+ SUBCATEGORY: 'Execute',
118
+ COMPONENT_CATEGORY: 'DB Query',
119
+ COMPONENT_DETAIL: 'Execute SQL Query',
120
+ SQL: sql,
121
+ PROVIDER: "mysql"
122
+ )
123
+ result = exec_update_without_apm(sql, name, binds)
124
+ StackifyRubyAPM.span name, TYPE, context: ctx do
125
+ return result
126
+ end
127
+ end
128
+
129
+ def exec_delete(sql, name, binds)
130
+ result = nil
131
+ unless StackifyRubyAPM.current_transaction
132
+ exec_delete_without_apm(sql, name, binds)
133
+ end
134
+ ctx = Span::Context.new(
135
+ CATEGORY: 'Database',
136
+ SUBCATEGORY: 'Execute',
137
+ COMPONENT_CATEGORY: 'DB Query',
138
+ COMPONENT_DETAIL: 'Execute SQL Query',
139
+ SQL: sql,
140
+ PROVIDER: "mysql"
141
+ )
142
+ result = exec_delete_without_apm(sql, name, binds)
143
+ StackifyRubyAPM.span name, TYPE, context: ctx do
144
+ return result
145
+ end
146
+ end
147
+
148
+ def exec_query(sql, name = "SQL", binds = [], prepare: false)
149
+ result = nil
150
+ unless StackifyRubyAPM.current_transaction
151
+ exec_query_without_apm(sql, name, binds)
152
+ end
153
+ ctx = Span::Context.new(
154
+ CATEGORY: 'Database',
155
+ SUBCATEGORY: 'Execute',
156
+ COMPONENT_CATEGORY: 'DB Query',
157
+ COMPONENT_DETAIL: 'Execute SQL Query',
158
+ SQL: sql,
159
+ PROVIDER: "mysql"
160
+ )
161
+ result = exec_query_without_apm(sql, name, binds)
162
+ StackifyRubyAPM.span name, TYPE, context: ctx do
163
+ return result
164
+ end
165
+ end
166
+ end
167
+ end
168
+ # rubocop:enable Metrics/MethodLength
169
+ end
170
+ end
171
+ if ActiveRecord::VERSION::MAJOR.to_i >= 5
172
+ register 'ActiveRecord::ConnectionAdapters::MySQL::DatabaseStatements', 'active_record/connection_adapters/mysql/database_statements', MysqlAdapterSpy.new
173
+ else
174
+ register 'ActiveRecord::ConnectionAdapters::Mysql2Adapter', 'active_record/connection_adapters/mysql2_adapter', MysqlAdapterSpy.new
175
+ end
176
+ end
177
+ end