rookout 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +12 -0
  3. data/bin/rookout +30 -0
  4. data/lib/rookout.rb +18 -0
  5. data/lib/rookout/augs/actions/action.rb +11 -0
  6. data/lib/rookout/augs/actions/action_run_processor.rb +29 -0
  7. data/lib/rookout/augs/aug.rb +121 -0
  8. data/lib/rookout/augs/aug_factory.rb +69 -0
  9. data/lib/rookout/augs/aug_rate_limiter.rb +96 -0
  10. data/lib/rookout/augs/augs_manager.rb +77 -0
  11. data/lib/rookout/augs/conditions/condition.rb +15 -0
  12. data/lib/rookout/augs/locations/location.rb +11 -0
  13. data/lib/rookout/augs/locations/location_file_line.rb +26 -0
  14. data/lib/rookout/com_ws/agent_com_ws.rb +221 -0
  15. data/lib/rookout/com_ws/backoff.rb +35 -0
  16. data/lib/rookout/com_ws/command_handler.rb +22 -0
  17. data/lib/rookout/com_ws/git.rb +53 -0
  18. data/lib/rookout/com_ws/information.rb +85 -0
  19. data/lib/rookout/com_ws/output.rb +135 -0
  20. data/lib/rookout/com_ws/token_bucket.rb +36 -0
  21. data/lib/rookout/config.rb +55 -0
  22. data/lib/rookout/exceptions.rb +140 -0
  23. data/lib/rookout/interface.rb +140 -0
  24. data/lib/rookout/logger.rb +158 -0
  25. data/lib/rookout/processor/namespace_serializer.rb +26 -0
  26. data/lib/rookout/processor/namespaces/container_namespace.rb +45 -0
  27. data/lib/rookout/processor/namespaces/frame_namespace.rb +65 -0
  28. data/lib/rookout/processor/namespaces/namespace.rb +28 -0
  29. data/lib/rookout/processor/namespaces/noop_namespace.rb +32 -0
  30. data/lib/rookout/processor/namespaces/ruby_object_namespace.rb +97 -0
  31. data/lib/rookout/processor/namespaces/ruby_object_serializer.rb +208 -0
  32. data/lib/rookout/processor/namespaces/ruby_utils_namespace.rb +65 -0
  33. data/lib/rookout/processor/namespaces/stack_namespace.rb +30 -0
  34. data/lib/rookout/processor/namespaces/traceback_namespace.rb +40 -0
  35. data/lib/rookout/processor/operations/operation.rb +11 -0
  36. data/lib/rookout/processor/operations/set_operation.rb +48 -0
  37. data/lib/rookout/processor/paths/arithmetic_path.rb +84 -0
  38. data/lib/rookout/processor/paths/canopy/actions.rb +184 -0
  39. data/lib/rookout/processor/paths/canopy/consts.rb +82 -0
  40. data/lib/rookout/processor/paths/canopy/maps.rb +2837 -0
  41. data/lib/rookout/processor/paths/canopy/markers.rb +186 -0
  42. data/lib/rookout/processor/paths/path.rb +15 -0
  43. data/lib/rookout/processor/processor.rb +34 -0
  44. data/lib/rookout/processor/processor_factory.rb +23 -0
  45. data/lib/rookout/processor/rook_error.rb +45 -0
  46. data/lib/rookout/protobuf/.gitignore +0 -0
  47. data/lib/rookout/protobuf/agent_info_pb.rb +68 -0
  48. data/lib/rookout/protobuf/controller_info_pb.rb +29 -0
  49. data/lib/rookout/protobuf/envelope_pb.rb +21 -0
  50. data/lib/rookout/protobuf/messages_pb.rb +189 -0
  51. data/lib/rookout/protobuf/variant_pb.rb +139 -0
  52. data/lib/rookout/rookout_singleton.rb +73 -0
  53. data/lib/rookout/services/position.rb +163 -0
  54. data/lib/rookout/services/tracer.rb +83 -0
  55. data/lib/rookout/trigger_services.rb +34 -0
  56. data/lib/rookout/user_warnings.rb +25 -0
  57. data/lib/rookout/version.rb +4 -0
  58. metadata +269 -0
@@ -0,0 +1,135 @@
1
+ module Rookout
2
+ module ComWs
3
+ require_relative "../logger"
4
+ require_relative "../config"
5
+
6
+ require_relative "../protobuf/messages_pb"
7
+
8
+ require_relative "../processor/namespaces/ruby_object_namespace"
9
+ require_relative "../processor/namespace_serializer"
10
+
11
+ require_relative "token_bucket"
12
+
13
+ class Output
14
+ def initialize
15
+ @agent_id = nil
16
+ @agent_com = nil
17
+
18
+ @rule_status_update_bucket = TokenBucket.new Config.output_max_status_updates,
19
+ Config.output_bucket_refresh_rate do
20
+ Logger.instance.error "Limit reached, dropping status updates"
21
+ end
22
+
23
+ @user_message_bucket = TokenBucket.new Config.output_max_aug_messages,
24
+ Config.output_bucket_refresh_rate do
25
+ Logger.instance.error "Limit reached, dropping aug report messages"
26
+ end
27
+
28
+ @log_message_bucket = TokenBucket.new Config.output_max_log_items,
29
+ Config.output_bucket_refresh_rate do
30
+ internal_send_log_message 3,
31
+ Time.new,
32
+ __FILE__,
33
+ 0,
34
+ text,
35
+ "Limit reached, dropping log messages",
36
+ "Limit reached, dropping log messages"
37
+ end
38
+
39
+ Logger.instance.register_output self
40
+
41
+ @closing = false
42
+ end
43
+
44
+ attr_accessor :agent_id, :agent_com
45
+
46
+ def close
47
+ @closing = true
48
+
49
+ Logger.instance.remove_output self
50
+ end
51
+
52
+ def send_warning rule_id, error
53
+ send_rule_status rule_id, :Warning, error
54
+ end
55
+
56
+ def send_rule_status rule_id, active, error
57
+ return if @closing || !@agent_com
58
+
59
+ @rule_status_update_bucket.if_available do
60
+ protobuf_error = nil
61
+ if error
62
+ protobuf_error = Com::Rookout::Error.new message: error.messsage,
63
+ type: error.type
64
+ protobuf_error.parameters.copy_from error.parameters
65
+ protobuf_error.exc.copy_from error.exc
66
+ protobuf_error.traceback.copy_from error.traceback
67
+ end
68
+
69
+ status = Com::Rookout::RuleStatusMessage.new agent_id: @agent_id,
70
+ rule_id: rule_id,
71
+ active: active,
72
+ error: protobuf_error
73
+ @agent_com.add status
74
+ end
75
+ end
76
+
77
+ def send_user_message aug_id, report_id, arguments
78
+ return if @closing || !@agent_com
79
+
80
+ @user_message_bucket.if_available do
81
+ if arguments.nil? || arguments.call_method("size", "") == 0
82
+ protobuf_arguments = nil
83
+ else
84
+ protobuf_arguments = Processor::NamespaceSerializer.dump arguments
85
+ end
86
+
87
+ msg = Com::Rookout::AugReportMessage.new agent_id: @agent_id,
88
+ aug_id: aug_id,
89
+ report_id: report_id,
90
+ arguments: protobuf_arguments
91
+ @agent_com.add msg
92
+ end
93
+ end
94
+
95
+ def send_log_message level, time, filename, lineno, text, formatted_message, arguments
96
+ return if @closing || !@agent_com
97
+ @log_message_bucket.if_available do
98
+ internal_send_log_message level, time, filename, lineno, text, formatted_message, arguments
99
+ end
100
+ end
101
+
102
+ def flush_messages
103
+ return unless @agent_com
104
+ @agent_com.flush
105
+ end
106
+
107
+ private
108
+
109
+ def internal_send_log_message level, time, filename, lineno, text, formatted_message, arguments
110
+ return if @closing || !@agent_com
111
+
112
+ timestamp = Google::Protobuf::Timestamp.new
113
+ timestamp.from_time time
114
+
115
+ if arguments.nil? || arguments.empty?
116
+ protobuf_arguments = nil
117
+ else
118
+ protobuf_arguments = Processor::NamespaceSerializer.dump(
119
+ Processor::Namespaces::RubyObjectNamespace.new(arguments)
120
+ )
121
+ end
122
+
123
+ msg = Com::Rookout::LogMessage.new timestamp: timestamp,
124
+ agent_id: @agent_id,
125
+ level: level,
126
+ filename: filename,
127
+ line: lineno,
128
+ text: text,
129
+ formatted_message: formatted_message,
130
+ legacy_arguments: protobuf_arguments
131
+ @agent_com.add msg
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,36 @@
1
+ module Rookout
2
+ module ComWs
3
+ class TokenBucket
4
+ def initialize limit, interval_seconds, &do_once_when_exhausted
5
+ @initial_limit = limit
6
+ @remaining = limit
7
+ @last_reset = Time.new
8
+ @interval = interval_seconds
9
+ @do_once_when_exhausted = do_once_when_exhausted
10
+ @do_once_when_exhausted_performed = false
11
+ end
12
+
13
+ def exhausted?
14
+ if Time.new - @last_reset > @interval
15
+ @last_reset = Time.new
16
+ @remaining = @initial_limit
17
+ @do_once_when_exhausted_performed = false
18
+ end
19
+
20
+ @remaining < 0
21
+ end
22
+
23
+ def if_available
24
+ @remaining -= 1
25
+ if exhausted?
26
+ return if @do_once_when_exhausted_performed || @do_once_when_exhausted.nil?
27
+
28
+ @do_once_when_exhausted.call
29
+ @do_once_when_exhausted_performed = true
30
+ else
31
+ yield
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,55 @@
1
+ module Rookout
2
+ require_relative "version"
3
+
4
+ module Config
5
+ # Magic to allow for module variables to be easily accessible
6
+ class << self
7
+ attr_accessor :debug
8
+ Rookout::Config.debug = false
9
+
10
+ attr_accessor :logger_filename, :logger_log_to_stderr, :logger_log_level
11
+ Rookout::Config.logger_filename = "rookout/ruby-rook.log".freeze
12
+ Rookout::Config.logger_log_to_stderr = false
13
+ Rookout::Config.logger_log_level = :info
14
+
15
+ attr_accessor :agent_com_configuration_command_thread_name,
16
+ :agent_com_max_message_limit,
17
+ :agent_com_timeout,
18
+ :agent_com_ping_interval,
19
+ :agent_com_ping_timeout,
20
+ :agent_com_flush_timeout,
21
+ :agent_com_max_queued_messages
22
+ Rookout::Config.agent_com_max_message_limit = 1024 * 1024
23
+ Rookout::Config.agent_com_timeout = 3
24
+ Rookout::Config.agent_com_ping_interval = 10
25
+ Rookout::Config.agent_com_ping_timeout = 30
26
+ Rookout::Config.agent_com_flush_timeout = 3
27
+ Rookout::Config.agent_com_max_queued_messages = 500
28
+
29
+ attr_accessor :backoff_factor, :backoff_reset_time, :backoff_max_time
30
+ Rookout::Config.backoff_factor = 0.2
31
+ Rookout::Config.backoff_max_time = 60
32
+ Rookout::Config.backoff_reset_time = 3 * 60.0
33
+
34
+ attr_accessor :output_max_status_updates,
35
+ :output_max_aug_messages,
36
+ :output_max_log_items,
37
+ :output_bucket_refresh_rate
38
+ Rookout::Config.output_max_status_updates = 200
39
+ Rookout::Config.output_max_aug_messages = 100
40
+ Rookout::Config.output_max_log_items = 200
41
+ Rookout::Config.output_bucket_refresh_rate = 10
42
+
43
+ attr_accessor :instrumentation_max_aug_time
44
+ Rookout::Config.instrumentation_max_aug_time = 400
45
+
46
+ attr_accessor :user_git_commit, :user_git_origin
47
+ Rookout::Config.user_git_commit = nil
48
+ Rookout::Config.user_git_origin = nil
49
+
50
+ attr_accessor :rookout_version, :rookout_commit
51
+ Rookout::Config.rookout_version = Rookout::VERSION
52
+ Rookout::Config.rookout_commit = Rookout::COMMIT
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,140 @@
1
+ module Rookout
2
+ module Exceptions
3
+ class ToolException < RuntimeError
4
+ def initialize msg, parameters = {}, cause = nil
5
+ super msg
6
+ @parameters = parameters
7
+ @cause = cause
8
+ end
9
+
10
+ attr_reader :parameters
11
+ end
12
+
13
+ class RookInterfaceException < ToolException
14
+ def initialize message
15
+ super message
16
+ end
17
+ end
18
+
19
+ class RookVersionNotSupported < ToolException
20
+ def initialize parameter, value
21
+ super "Rook is not supported in this #{parameter}: #{value}"
22
+ end
23
+ end
24
+
25
+ class RookAugInvalidKey < ToolException
26
+ def initialize key, configuration
27
+ super "Failed to get key #{key} from configuration #{configuration}",
28
+ { key: key, configuration: configuration }
29
+ end
30
+ end
31
+
32
+ class RookAttributeNotFound < ToolException
33
+ def initialize attribute
34
+ super "Failed to get attribute #{attribute}", { attribute: attribute }
35
+ end
36
+ end
37
+
38
+ class RookKeyNotFound < ToolException
39
+ def initialize key
40
+ super "Failed to get key #{key}", { key: key }
41
+ end
42
+ end
43
+
44
+ class RookWriteAttributeNotSupported < ToolException
45
+ def initialize namespace_type, attribute
46
+ super "Namespace #{namespace_type} does not support write",
47
+ { namespace: namespace_type, attribute: attribute }
48
+ end
49
+ end
50
+
51
+ class RookMethodNotFound < ToolException
52
+ def initialize namespace_type, method
53
+ super "Namespace method not found #{method}",
54
+ { namespace: namespace_type, method: method }
55
+ end
56
+ end
57
+
58
+ class RookInvalidObjectForAccess < ToolException
59
+ def initialize object_type, access
60
+ super "Object #{object_type} does not support access #{access}",
61
+ { object: object_type, access: access }
62
+ end
63
+ end
64
+
65
+ class RookInvalidArithmeticPath < ToolException
66
+ def initialize path, cause = nil
67
+ super "Invalid arithmetic path configuration. configuration: #{path}, innerException: #{cause}",
68
+ { configuration: path },
69
+ cause
70
+ end
71
+ end
72
+
73
+ class RookOperationReadOnly < ToolException
74
+ def initialize type
75
+ super "Operation does not support write: #{type}", { operation: type }
76
+ end
77
+ end
78
+
79
+ class RookExceptionEvaluationFailed < ToolException
80
+ def initialize expression, cause
81
+ super "Failed to evaluate expression %s: #{expression}", { expression: expression }, cause
82
+ end
83
+ end
84
+
85
+ class RookNonPrimitiveObjectType < ToolException
86
+ def initialize path
87
+ super "Object %s must be of primitive type, such as: string, int, long etc: #{path}", { path: path }
88
+ end
89
+ end
90
+
91
+ class RookMessageSizeExceeded < ToolException
92
+ def initialize message_size, max_message_size
93
+ super "Message size of #{message_size} exceeds max size limit of #{max_message_size}. " \
94
+ "Change the depth of collection or change the default by setting ROOKOUT_MAX_MESSAGE_SIZE " \
95
+ "as environment variable or system property",
96
+ { message_size: message_size, max_message_size: max_message_size }
97
+ end
98
+ end
99
+
100
+ class RookRuleRateLimited < ToolException
101
+ def initialize
102
+ super "Breakpoint was disabled due to rate-limiting. " \
103
+ "For more information: https://docs.rookout.com/docs/breakpoints-tasks.html#rate-limiting"
104
+ end
105
+ end
106
+
107
+ class RookInvalidToken < ToolException
108
+ def initialize token
109
+ super "The Rookout token supplied #{token[0..6]} is not valid; please check the token and try again",
110
+ { token: token[0...6] }
111
+ end
112
+ end
113
+
114
+ class RookSourceFilePathSuggestion < ToolException
115
+ def initialize wanted_path, matching_path
116
+ super "Rookout found alternative file path: #{matching_path}",
117
+ { wanted_path: wanted_path, matching_path: matching_path }
118
+ end
119
+ end
120
+
121
+ class RookSetTracepointFailed < ToolException
122
+ def initialize target_line, error
123
+ super "Failed to set TracePoint on line #{target_line} due to #{error.message}",
124
+ { target_line: target_line, error: error }
125
+ end
126
+ end
127
+
128
+ class RookObjectCannotBeSerialized < ToolException
129
+ def initialize object, message
130
+ super message, { "class" => object.class.to_s }
131
+ end
132
+ end
133
+
134
+ class RookMissingToken < ToolException
135
+ def initialize
136
+ super "No Rookout token was supplied. Make sure to pass the Rookout Token when starting the rook"
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,140 @@
1
+ module Rookout
2
+ class Interface
3
+ require "singleton"
4
+ include Singleton
5
+
6
+ require_relative "config"
7
+ require_relative "exceptions"
8
+
9
+ def initialize
10
+ @rook = nil
11
+ end
12
+
13
+ def start options = {}
14
+ return unless @rook.nil?
15
+ throw_errors = options[:throw_errors] == true
16
+ Config.debug = evaluate_flag options[:debug], "ROOKOUT_DEBUG"
17
+
18
+ begin
19
+ require_relative "rookout_singleton"
20
+
21
+ configure_logging options
22
+ labels = options[:labels] || parse_labels(ENV["ROOKOUT_LABELS"])
23
+ validate_labels labels
24
+
25
+ configure_git options
26
+
27
+ com = configure_com options
28
+
29
+ async_start = true? ENV["ROOKOUT_ASYNC_START"]
30
+ fork = evaluate_flag options[:fork], "ROOKOUT_ENABLE_FORK"
31
+
32
+ print_config com, labels: labels, async_start: async_start, fork: fork if Config.debug
33
+
34
+ rook = RookoutSingleton.instance
35
+ rook.connect com[:token], com[:host], com[:port], com[:proxy], labels, async_start, fork
36
+
37
+ # TODO: ADD DETAILED PROCESSING FOR SPECIFIC ERRORS
38
+ rescue Exception => e
39
+ puts e.full_message if Config.debug
40
+ raise if throw_errors
41
+ end
42
+ end
43
+
44
+ def flush
45
+ @rook.flush unless @rook.nil?
46
+ end
47
+
48
+ def stop
49
+ return if @rook.nil?
50
+
51
+ @rook.stop
52
+ @rook = nil
53
+ end
54
+
55
+ private
56
+
57
+ TRUE_VALUE = [true, "y", "Y", "yes", "Yes", "YES", "true", "True", "TRUE", "1"].freeze
58
+
59
+ def configure_logging options
60
+ if Config.debug
61
+ log_to_stderr = true
62
+ log_level = "DEBUG"
63
+ else
64
+ log_to_stderr = evaluate_flag options[:log_to_stderr], "ROOKOUT_LOG_TO_STDERR"
65
+ log_level = options[:log_level] || ENV["ROOKOUT_LOG_FILE"]
66
+ end
67
+
68
+ log_file = options[:log_file] || ENV["ROOKOUT_LOG_FILE"]
69
+
70
+ Config.logger_log_to_stderr = log_to_stderr unless log_to_stderr.nil?
71
+ Config.logger_filename = log_file unless log_file.nil?
72
+ Config.logger_log_level = log_level unless log_level.nil?
73
+ end
74
+
75
+ def configure_git options
76
+ Config.user_git_origin = options[:git_origin] if options[:git_origin]
77
+ Config.user_git_commit = options[:git_commit] if options[:git_commit]
78
+ end
79
+
80
+ def configure_com options
81
+ host = evaluate_config options[:host], "ROOKOUT_CONTROLLER_HOST", "wss://control.rookout.com"
82
+ port = evaluate_config options[:port], "ROOKOUT_CONTROLLER_PORT", 443
83
+ proxy = evaluate_config options[:proxy], "ROOKOUT_PROXY"
84
+ token = evaluate_config options[:token], "ROOKOUT_TOKEN"
85
+
86
+ raise Exceptions::RookMissingToken if token.nil? && host == "wss://control.rookout.com"
87
+ verify_token token if token
88
+
89
+ { host: host, port: port, proxy: proxy, token: token }
90
+ end
91
+
92
+ def evaluate_flag argument, env_var_name
93
+ return true? argument unless argument.nil?
94
+ true? ENV[env_var_name]
95
+ end
96
+
97
+ def true? value
98
+ TRUE_VALUE.include? value
99
+ end
100
+
101
+ def evaluate_config argument, env_var_name, default = nil
102
+ return argument unless argument.nil?
103
+ return ENV[env_var_name] unless ENV[env_var_name].nil?
104
+ default
105
+ end
106
+
107
+ def parse_labels raw_labels
108
+ labels = {}
109
+ return labels if raw_labels.nil?
110
+
111
+ raw_labels.split(",").each do |raw_label|
112
+ keyvalue = raw_label.gsub(/^[( "')*]/, "").gsub(/[( "')*]$/, "").split(":")
113
+ if keyvalue.length == 2
114
+ labels[keyvalue[0]] = keyvalue[1]
115
+ end
116
+ end
117
+ labels
118
+ end
119
+
120
+ def validate_labels labels
121
+ labels.each do |label_name, _|
122
+ if label_name.start_with? "$"
123
+ raise Exceptions::RookInvalidLabel, label_name
124
+ end
125
+ end
126
+ end
127
+
128
+ def verify_token token
129
+ raise Exceptions::RookInvalidOptions, "Rookout token should be a String" unless token.is_a? String
130
+ raise Exceptions::RookInvalidOptions, "Rookout token should be 64 characters" unless token.length == 64
131
+ raise Exceptions::RookInvalidOptions, "Rookout token must consist of only hexadecimal characters" unless
132
+ token.match(/^[0-9a-zA-Z]{0,64}$/)
133
+ end
134
+
135
+ def print_config com, options
136
+ puts "[Rookout] Communication Options: #{com}"
137
+ puts "[Rookout] Other Options #{options}"
138
+ end
139
+ end
140
+ end