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
@@ -0,0 +1,96 @@
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 PostgresqlAdapterSpy
9
+ TYPE = 'db.sinatra_active_record.sql'.freeze
10
+
11
+ # rubocop:disable Metrics/MethodLength
12
+ def install
13
+ ActiveRecord::ConnectionAdapters::PostgreSQL::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
+
21
+ unless StackifyRubyAPM.current_transaction
22
+ exec_update_without_apm(sql, name, binds)
23
+ end
24
+
25
+ ctx = Span::Context.new(
26
+ CATEGORY: 'Database',
27
+ SUBCATEGORY: 'Execute',
28
+ COMPONENT_CATEGORY: 'DB Query',
29
+ COMPONENT_DETAIL: 'Execute SQL Query',
30
+ SQL: sql,
31
+ PROVIDER: "mysql"
32
+ )
33
+
34
+ result = exec_update_without_apm(sql, name, binds)
35
+
36
+ StackifyRubyAPM.span name, TYPE, context: ctx do
37
+ return result
38
+ end
39
+ end
40
+
41
+ def exec_delete(sql, name = nil, binds = [])
42
+ result = nil
43
+
44
+ unless StackifyRubyAPM.current_transaction
45
+ exec_delete_without_apm(sql, name, binds)
46
+ end
47
+
48
+ ctx = Span::Context.new(
49
+ CATEGORY: 'Database',
50
+ SUBCATEGORY: 'Execute',
51
+ COMPONENT_CATEGORY: 'DB Query',
52
+ COMPONENT_DETAIL: 'Execute SQL Query',
53
+ SQL: sql,
54
+ PROVIDER: "postgresql"
55
+ )
56
+
57
+ result = exec_delete_without_apm(sql, name, binds)
58
+
59
+ StackifyRubyAPM.span name, TYPE, context: ctx do
60
+ return result
61
+ end
62
+ end
63
+
64
+
65
+ def exec_query(sql, name = "SQL", binds = [], prepare: false)
66
+ result = nil
67
+
68
+ unless StackifyRubyAPM.current_transaction
69
+ exec_query_without_apm(sql, name, binds)
70
+ end
71
+
72
+ ctx = Span::Context.new(
73
+ CATEGORY: 'Database',
74
+ SUBCATEGORY: 'Execute',
75
+ COMPONENT_CATEGORY: 'DB Query',
76
+ COMPONENT_DETAIL: 'Execute SQL Query',
77
+ SQL: sql,
78
+ PROVIDER: "postgresql"
79
+ )
80
+
81
+ result = exec_query_without_apm(sql, name, binds)
82
+
83
+ StackifyRubyAPM.span name, TYPE, context: ctx do
84
+ return result
85
+ end
86
+ end
87
+
88
+ end
89
+ end
90
+ # rubocop:enable Metrics/MethodLength
91
+ end
92
+
93
+ register 'ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements', 'active_record/connection_adapters/postgresql/database_statements', PostgresqlAdapterSpy.new
94
+ end
95
+ end
96
+
@@ -0,0 +1,48 @@
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 SqliteAdapterSpy
9
+ TYPE = 'db.sinatra_active_record.sql'.freeze
10
+
11
+ # rubocop:disable Metrics/MethodLength
12
+ def install
13
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.class_eval do
14
+ alias exec_query_without_apm exec_query
15
+
16
+
17
+ def exec_query(sql, name = "SQL", binds = [], prepare: false)
18
+ result = nil
19
+
20
+ unless StackifyRubyAPM.current_transaction
21
+ exec_query_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: "sqlite"
31
+ )
32
+
33
+ result = exec_query_without_apm(sql, name, binds)
34
+
35
+ StackifyRubyAPM.span name, TYPE, context: ctx do
36
+ return result
37
+ end
38
+ end
39
+
40
+ end
41
+ end
42
+ # rubocop:enable Metrics/MethodLength
43
+ end
44
+
45
+ register 'ActiveRecord::ConnectionAdapters::SQLite3Adapter', 'active_record/connection_adapters/sqlite3_adapter', SqliteAdapterSpy.new
46
+ end
47
+ end
48
+
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
+ #
3
+ # Monkey patch for the Tilt::Template class on template rendering and span creation.
4
+ #
2
5
 
3
6
  module StackifyRubyAPM
4
7
  # @api private
5
8
  module Spies
6
9
  # @api private
7
10
  class TiltSpy
8
- # This class monkeypatch the Tilt::Template class with render method.
11
+
9
12
  TYPE = 'template.tilt'.freeze
10
13
 
11
14
  def install
@@ -15,6 +18,8 @@ module StackifyRubyAPM
15
18
  def render(*args, &block)
16
19
  name = options[:__stackify_apm_template_name] || 'Unknown template'
17
20
 
21
+ # Creates new span for Tilt templating
22
+ #
18
23
  StackifyRubyAPM.span name, TYPE do
19
24
  render_without_apm(*args, &block)
20
25
  end
@@ -23,6 +28,8 @@ module StackifyRubyAPM
23
28
  end
24
29
  end
25
30
 
31
+ # Registers Tilt spy, go to: /stackify/spies.rb
32
+ #
26
33
  register 'Tilt::Template', 'tilt/template', TiltSpy.new
27
34
  end
28
35
  end
@@ -21,7 +21,7 @@ module StackifyRubyAPM
21
21
 
22
22
  attr_reader :config
23
23
 
24
- def build(backtrace, type:)
24
+ def build(backtrace, type)
25
25
  Stacktrace.new.tap do |s|
26
26
  s.frames = backtrace.map do |line|
27
27
  @cache[[line, type]]
@@ -36,6 +36,7 @@ module StackifyRubyAPM
36
36
  line, type = keys
37
37
  abs_path, lineno, function, _module_name = parse_line(line)
38
38
  current_filename = strip_load_path(abs_path)
39
+ library_frame = library_frame?(config, abs_path)
39
40
 
40
41
  frame = Stacktrace::Frame.new
41
42
  #frame.abs_path = abs_path
@@ -45,7 +46,7 @@ module StackifyRubyAPM
45
46
  #frame.library_frame = library_frame?(config, abs_path)
46
47
 
47
48
  line_count =
48
- context_lines_for(config, type, library_frame: frame.library_frame)
49
+ context_lines_for(config, type, library_frame)
49
50
  frame.build_context line_count
50
51
 
51
52
  cache[[line, type]] = frame
@@ -93,7 +94,7 @@ module StackifyRubyAPM
93
94
  prefix ? path[prefix.chomp(File::SEPARATOR).length + 1..-1] : path
94
95
  end
95
96
 
96
- def context_lines_for(config, type, library_frame:)
97
+ def context_lines_for(config, type, library_frame)
97
98
  key = "source_lines_#{type}_#{library_frame ? 'library' : 'app'}_frames"
98
99
  config.send(key.to_sym)
99
100
  end
@@ -13,7 +13,7 @@ module StackifyRubyAPM
13
13
  include Log
14
14
 
15
15
  def initialize(agent)
16
- debug '@stackify_ruby [Subscriber] [lib/subscriber.rb] initialize()'
16
+ debug '[Subscriber] initialize()'
17
17
  debug agent.inspect
18
18
  @agent = agent
19
19
  @normalizers = Normalizers.build(agent.config)
@@ -38,7 +38,7 @@ module StackifyRubyAPM
38
38
  # [call] Called when the rails version is 3.x
39
39
  def call(name, started, finished, id, payload)
40
40
  return unless (transaction = @agent.current_transaction)
41
- debug '@stackify_ruby [Subscriber] [lib/subscriber.rb] call():'
41
+ debug '[Subscriber] call():'
42
42
  debug id
43
43
  debug name
44
44
  debug transaction
@@ -71,7 +71,7 @@ module StackifyRubyAPM
71
71
  # [start] Called when the rails version is NOT 3.x
72
72
  def start(name, id, payload)
73
73
  return unless (transaction = @agent.current_transaction)
74
- debug '@stackify_ruby [Subscriber] [lib/subscriber.rb] start():'
74
+ debug '[Subscriber] start():'
75
75
  debug id
76
76
  debug name
77
77
  debug transaction
@@ -91,7 +91,7 @@ module StackifyRubyAPM
91
91
  # [finish] Called when the rails version is NOT 3.x
92
92
  def finish(_name, id, _payload)
93
93
  # debug "AS::Notification#finish:#{name}:#{id}"
94
- debug '@stackify_ruby [Subscriber] [lib/subscriber.rb] finish():'
94
+ debug '[Subscriber] finish():'
95
95
  return unless (transaction = @agent.current_transaction)
96
96
 
97
97
  while (notification = transaction.notifications.pop)
@@ -22,7 +22,7 @@ module StackifyRubyAPM
22
22
  attr_accessor :trace_file_counter, :process_id
23
23
 
24
24
  def post(transactions = [])
25
-
25
+
26
26
  # convert transactions to json
27
27
  json_traces = []
28
28
  transactions.each do |transaction|
@@ -36,31 +36,14 @@ module StackifyRubyAPM
36
36
  pid = $PID || Process.pid
37
37
  host_name = @config.hostname || `hostname`
38
38
  date_now = Time.now
39
- current_time = date_now.strftime("%Y-%m-%d, %H:%M:%S.%6N")
40
- trace_path = @config.log_trace_path
41
- trace_file_result = @config.check_lastlog_needs_new(trace_path)
42
- current_trace_file = trace_file_result['latest_file']
43
-
44
- if trace_file_result['new_flagger'] == true
45
- @trace_file_counter = @trace_file_counter + 1
46
- file_ctr = @trace_file_counter
47
- current_trace_file = trace_path + host_name + "#" + pid.to_s + "-" + file_ctr.to_s + ".log"
48
- else
49
- if @process_id != pid
50
- @trace_file_counter = 1
51
- @process_id = pid
52
- file_ctr = @trace_file_counter
53
- current_trace_file = trace_path + host_name + "#" + pid.to_s + "-" + file_ctr.to_s + ".log"
54
- end
55
- end
39
+ current_datetime = date_now.strftime("%Y-%m-%d, %H:%M:%S.%6N")
56
40
 
57
- current_trace_file = current_trace_file.gsub(/[[:space:]]/, '')
58
- open(current_trace_file, 'a') {|f|
41
+ if(ENV['STACKIFY_RUBY_ENV'] != 'rspec')
59
42
  json_traces.each do |json_trace|
60
- f.puts current_time + "> " + json_trace + "\n"
43
+ update_json = current_datetime + " > " + json_trace + "\n"
44
+ @config.tracer_logger.<<(update_json)
61
45
  end
62
- }
63
-
46
+ end
64
47
  end
65
48
  end
66
49
  end
@@ -24,6 +24,7 @@ module StackifyRubyAPM
24
24
 
25
25
  @notifications = [] # for AS::Notifications
26
26
  @context = context || Context.new
27
+ @exceptions = []
27
28
 
28
29
  yield self if block_given?
29
30
  end
@@ -31,7 +32,11 @@ module StackifyRubyAPM
31
32
 
32
33
  attr_accessor :name, :type, :http_status
33
34
  attr_reader :id, :context, :duration, :dropped_spans,
34
- :timestamp, :spans, :result, :notifications, :instrumenter
35
+ :timestamp, :spans, :result, :notifications, :instrumenter, :exceptions
36
+
37
+ def add_exception exception
38
+ @exceptions << exception
39
+ end
35
40
 
36
41
  def release
37
42
  @instrumenter.current_transaction = nil
@@ -60,6 +65,10 @@ module StackifyRubyAPM
60
65
 
61
66
  self
62
67
  end
68
+ # This method is being used in unit testing
69
+ def running_spans
70
+ spans.select(&:running?)
71
+ end
63
72
  # rubocop:disable Metrics/MethodLength
64
73
  def span name, type = nil, backtrace: nil, context: nil
65
74
  span = build_and_start_span(name, type, context, backtrace)
data/lib/stackify/util.rb CHANGED
@@ -2,22 +2,9 @@
2
2
  module StackifyRubyAPM
3
3
  # @api private
4
4
  module Util
5
- def self.nearest_minute(target = Time.now.utc)
6
- target - target.to_i % 60
7
- end
8
-
9
5
  def self.micros(target = Time.now.utc)
10
6
  target.to_i * 1_000_000 + target.usec
11
7
  end
12
-
13
- def self.inspect_transaction(transaction)
14
- Inspector.new.transaction transaction
15
- end
16
-
17
- def self.git_sha
18
- sha = `git rev-parse --verify HEAD 2>&1`.chomp
19
- $? && $?.success? ? sha : nil # rubocop:disable Style/SpecialGlobalVars
20
- end
21
8
  end
22
9
  end
23
10
  require 'stackify/util/inspector'
@@ -1,4 +1,4 @@
1
1
  # Sets the version of the APM
2
2
  module StackifyRubyAPM
3
- VERSION = '0.9.0'.freeze
3
+ VERSION = '1.0.0'.freeze
4
4
  end
@@ -83,13 +83,13 @@ module StackifyRubyAPM
83
83
  def collect_and_send_transactions
84
84
  return if pending_transactions.empty?
85
85
  transactions = collect_batched_transactions
86
- debug '@stackify_ruby [lib/worker.rb] Successfully collect and send transaction to the to trace logger.'
86
+ debug '[Worker] Successfully collect and send transaction to the to trace logger.'
87
87
  begin
88
88
  @trace_logger.post(transactions)
89
89
  rescue ::Exception => e
90
- debug 'Failed posting: '
91
- debug e.inspect
92
- debug e.backtrace.join("\n")
90
+ error '[Worker] Collect_and_send_transactions error:'
91
+ error e.inspect
92
+ error e.backtrace.join("\n")
93
93
  nil
94
94
  end
95
95
  end
@@ -98,7 +98,7 @@ module StackifyRubyAPM
98
98
  return if pending_transactions.empty?
99
99
  transactions = collect_batched_transactions
100
100
  payload = @serializers.errors.build_all([msg.error])
101
- debug '@stackify_ruby [lib/worker.rb] error'
101
+ debug '[Worker] post_error()'
102
102
  # @trace_logger.post(payload, transactions[0])
103
103
  end
104
104
 
@@ -15,10 +15,10 @@
15
15
  require 'stackify/log'
16
16
  require 'stackify/version'
17
17
  require 'stackify/util/dig'
18
-
19
18
  require 'stackify/agent'
19
+ # Checks ruby version
20
+ require (RUBY_VERSION.to_f >= 2.4) ? 'stackify/logger/logger_high_version' : 'stackify/logger/logger_lower_version'
20
21
  require 'stackify/config'
21
- require 'stackify/logger'
22
22
  require 'stackify/context'
23
23
  require 'stackify/instrumenter'
24
24
  require 'stackify/internal_error'
@@ -35,8 +35,6 @@ module StackifyRubyAPM
35
35
  # @param config [Config] An instance of Config
36
36
  # @return [Agent] The resulting [Agent]
37
37
  def self.start(config = {})
38
- # debug "Starts the StackifyRubyAPM Agent self.start()"
39
-
40
38
  Agent.start config
41
39
  end
42
40
 
@@ -73,7 +71,6 @@ module StackifyRubyAPM
73
71
  # @yield [Transaction] Optional block encapsulating transaction
74
72
  # @return [Transaction] Unless block given
75
73
  def self.transaction(name = nil, type = nil, context: nil, &block)
76
- # debug "@stackify_ruby [lib/stackify_ruby_apm.rb] loads self.transaction(name = nil, type = nil, context: nil, &block)"
77
74
  return (block_given? ? yield : nil) unless agent
78
75
 
79
76
  agent.transaction(name, type, context: context, &block)
@@ -105,7 +102,6 @@ module StackifyRubyAPM
105
102
  # @param rack_env [Rack::Env] A Rack env
106
103
  # @return [Context] The built context
107
104
  def self.build_context(rack_env)
108
- # debug "@stackify_ruby [lib/stackify_ruby_apm.rb] self.build_context(rack_env)"
109
105
  agent && agent.build_context(rack_env)
110
106
  end
111
107
 
data/run-test.sh ADDED
@@ -0,0 +1,75 @@
1
+ #!/bin/bash
2
+
3
+ function startContainers() {
4
+ echo "Starting up containers..."
5
+ docker-compose up -d -V --remove-orphans --force-recreate
6
+ }
7
+
8
+ function waitForContainers() {
9
+ echo 'Waiting for container startup 10s...'
10
+ sleep 5
11
+ echo 'Waiting for container startup 5s...'
12
+ sleep 5
13
+ }
14
+
15
+ function stopContainers() {
16
+ echo "Stopping Containers..."
17
+ docker-compose down --remove-orphans
18
+
19
+ # delete volumes
20
+ echo "Delete Volumes..."
21
+ docker volume rm stackify-ruby-apm_postgresdata &> /dev/null
22
+ docker volume rm stackify-ruby-apm_mysqldata &> /dev/null
23
+ docker volume rm stackify-ruby-apm_mongodata &> /dev/null
24
+ docker volume rm stackify-ruby-apm_mongodata_config &> /dev/null
25
+ echo "Deleted Volumes"
26
+ }
27
+
28
+ function rspec_on_multiple_versions(){
29
+ # test against multiple ruby versions
30
+ set -e
31
+
32
+ RUBY_VERSIONS=('2.0.0-p648' '2.1.10' '2.2.10' '2.3.7' '2.4.0' '2.5.1' '2.6.0-preview3')
33
+ # BUNDLER ERROR
34
+ # '2.0.0-p648' 2.1.10
35
+
36
+ for ver in "${RUBY_VERSIONS[@]}"
37
+ do
38
+ if ! rbenv versions | grep -w $ver; then
39
+ # remove stale rbenv shim before rehashing
40
+ SHIM=/home/$USER/.rbenv/shims/.rbenv-shim
41
+ if [ -f $SHIM ]; then
42
+ rm $SHIM
43
+ fi
44
+
45
+ rbenv install --verbose $ver
46
+ rbenv rehash
47
+ rbenv local $ver
48
+ gem install bundler -v '~> 1.16'
49
+ fi
50
+
51
+ rbenv local $ver
52
+ rbenv exec bundle update
53
+
54
+ echo "====================================================="
55
+ echo "$ver: Start Test"
56
+ echo "====================================================="
57
+
58
+ if ! bundle exec rspec spec/ --format documentation --fail-fast; then
59
+ echo ">>>Unit Test Error on Version: $ver<<<"
60
+ stopContainers
61
+ exit 1
62
+ else
63
+ echo "====================================================="
64
+ echo "$ver: End Test"
65
+ echo "====================================================="
66
+ fi
67
+ done
68
+ }
69
+
70
+
71
+ startContainers
72
+ waitForContainers
73
+
74
+ rspec_on_multiple_versions
75
+ stopContainers