stackify-api-ruby 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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +30 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +157 -0
  7. data/Rakefile +2 -0
  8. data/lib/generators/stackify/stackify_generator.rb +13 -0
  9. data/lib/generators/stackify/templates/stackify.rb +17 -0
  10. data/lib/stackify-api-ruby.rb +166 -0
  11. data/lib/stackify/authorization/authorizable.rb +61 -0
  12. data/lib/stackify/authorization/authorization_client.rb +31 -0
  13. data/lib/stackify/engine.rb +21 -0
  14. data/lib/stackify/env_details.rb +108 -0
  15. data/lib/stackify/error.rb +56 -0
  16. data/lib/stackify/errors_governor.rb +65 -0
  17. data/lib/stackify/http_client.rb +50 -0
  18. data/lib/stackify/logger_client.rb +71 -0
  19. data/lib/stackify/logger_proxy.rb +35 -0
  20. data/lib/stackify/logs_sender.rb +78 -0
  21. data/lib/stackify/metrics/metric.rb +68 -0
  22. data/lib/stackify/metrics/metric_aggregate.rb +52 -0
  23. data/lib/stackify/metrics/metrics.rb +88 -0
  24. data/lib/stackify/metrics/metrics_client.rb +238 -0
  25. data/lib/stackify/metrics/metrics_queue.rb +26 -0
  26. data/lib/stackify/metrics/metrics_sender.rb +32 -0
  27. data/lib/stackify/metrics/monitor.rb +34 -0
  28. data/lib/stackify/msgs_queue.rb +78 -0
  29. data/lib/stackify/rack/errors_catcher.rb +17 -0
  30. data/lib/stackify/schedule_task.rb +23 -0
  31. data/lib/stackify/scheduler.rb +79 -0
  32. data/lib/stackify/utils/backtrace.rb +36 -0
  33. data/lib/stackify/utils/configuration.rb +78 -0
  34. data/lib/stackify/utils/methods.rb +27 -0
  35. data/lib/stackify/utils/msg_object.rb +22 -0
  36. data/lib/stackify/version.rb +3 -0
  37. data/lib/stackify/workers/add_msg_worker.rb +9 -0
  38. data/lib/stackify/workers/auth_worker.rb +18 -0
  39. data/lib/stackify/workers/logs_sender_worker.rb +17 -0
  40. data/lib/stackify/workers/worker.rb +65 -0
  41. data/spec/spec_helper.rb +17 -0
  42. data/stackify-api-ruby.gemspec +25 -0
  43. metadata +137 -0
@@ -0,0 +1,26 @@
1
+ module Stackify::Metrics
2
+ class MetricsQueue < SizedQueue
3
+ include MonitorMixin
4
+
5
+ def initialize
6
+ super(Stackify.configuration.queue_max_size)
7
+ end
8
+
9
+ alias :old_push :push
10
+
11
+ def add_metric metric
12
+ self.synchronize do
13
+ self.old_push metric
14
+ end
15
+ end
16
+
17
+ alias :old_size :size
18
+
19
+ def size
20
+ self.synchronize do
21
+ self.old_size
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ module Stackify::Metrics
2
+ class MetricsSender < Stackify::HttpClient
3
+ SUBMIT_METRIS_URI = URI('https://dev.stackify.com/api/Metrics/SubmitMetricsByID')
4
+ GET_METRIC_INFO_URI = URI('https://dev.stackify.com/api/Metrics/GetMetricInfo')
5
+
6
+ def initialize
7
+ end
8
+
9
+ def monitor_info aggr_metric
10
+ if Stackify.authorized?
11
+ send_request GET_METRIC_INFO_URI, GetMetricRequest.new(aggr_metric).to_h.to_json
12
+ else
13
+ Stackify.log_internal_error "Getting of monitor_info is failed because of authorisation failure"
14
+ end
15
+ end
16
+
17
+ def upload_metrics aggr_metrics
18
+ return true if aggr_metrics.nil? || aggr_metrics.length == 0
19
+ if Stackify.authorized?
20
+ records = []
21
+ aggr_metrics.each_pair do |_key, metric|
22
+ records << Stackify::Metrics::MetricForSubmit.new(metric).to_h
23
+ prms = [metric.category, metric.name, metric.count, metric.value, metric.monitor_id ]
24
+ Stackify.internal_log :debug, 'Uploading metric: %s: %s count %s, value %s, ID %s' % prms
25
+ end
26
+ send_request SUBMIT_METRIS_URI, records.to_json
27
+ else
28
+ Stackify.log_internal_error "Uploading of metrics is failed because of authorisation failure"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ module Stackify::Metrics
2
+ class GetMetricRequest
3
+ attr_accessor :category, :metric_name, :device_id,
4
+ :device_app_id, :app_name_id, :metric_type_id
5
+
6
+ def initialize aggr_metric
7
+ @metric_name = aggr_metric.name
8
+ @metric_type_id = aggr_metric.metric_type
9
+ @category = aggr_metric.category
10
+ @device_app_id = Stackify::EnvDetails.instance.auth_info['DeviceAppID']
11
+ @device_id = Stackify::EnvDetails.instance.auth_info['DeviceID']
12
+ @app_name_id = Stackify::EnvDetails.instance.auth_info['AppNameID']
13
+ end
14
+
15
+ def to_h
16
+ {
17
+ 'DeviceAppID' => @device_app_id,
18
+ 'DeviceID' => @device_id,
19
+ 'AppNameID' => @app_name_id,
20
+ 'MetricName' => @metric_name,
21
+ 'MetricTypeID' => @metric_type_id,
22
+ 'Category' => @category
23
+ }
24
+ end
25
+ end
26
+
27
+ class GetMetricResponse
28
+ attr_accessor :monitor_id
29
+ def initialize
30
+ end
31
+ end
32
+
33
+
34
+ end
@@ -0,0 +1,78 @@
1
+ module Stackify
2
+ class MsgsQueue < SizedQueue
3
+ include MonitorMixin
4
+ #TODO: restrict possibility to work with class if app is off
5
+ CHUNK_MIN_WEIGHT = 50
6
+ ERROR_SIZE = 10
7
+ LOG_SIZE = 1
8
+ DELAY_WAITING = 2
9
+
10
+ def initialize
11
+ super(Stackify.configuration.queue_max_size)
12
+ reset_current_chunk
13
+ end
14
+
15
+ alias :old_push :push
16
+
17
+ def push_remained_msgs
18
+ wait_until_all_workers_will_add_msgs
19
+ self.synchronize do
20
+ push_current_chunk
21
+ Stackify.shutdown_all
22
+ if self.length > 0
23
+ Stackify.logs_sender.send_remained_msgs
24
+ Stackify.internal_log :info, 'All remained logs are sent'
25
+ Stackify.status = Stackify::STATUSES[:terminated]
26
+ end
27
+ end
28
+ end
29
+
30
+ def add_msg msg
31
+ self.synchronize do
32
+ if msg.is_a?(Hash)
33
+ @current_chunk_weight += msg['Ex'].nil? ? LOG_SIZE : ERROR_SIZE
34
+ @current_chunk << msg
35
+ if @current_chunk_weight >= CHUNK_MIN_WEIGHT
36
+ self.old_push(@current_chunk)
37
+ reset_current_chunk
38
+ end
39
+ else
40
+ Stackify.log_internal_error "MsgsQueue: add_msg should get hash, but not a #{msg.class}"
41
+ end
42
+ end
43
+ end
44
+
45
+ alias :<< :add_msg
46
+ alias :push :add_msg
47
+
48
+ def pop_all
49
+ self.synchronize do
50
+ msgs = []
51
+ until self.empty? do
52
+ msgs << self.pop
53
+ end
54
+ msgs
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def reset_current_chunk
61
+ @current_chunk = []
62
+ @current_chunk_weight = 0
63
+ end
64
+
65
+ def wait_until_all_workers_will_add_msgs
66
+ while Stackify.alive_adding_msg_workers.size > 0
67
+ sleep DELAY_WAITING
68
+ end
69
+ end
70
+
71
+ def push_current_chunk
72
+ unless @current_chunk.empty?
73
+ self.old_push(@current_chunk)
74
+ reset_current_chunk
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,17 @@
1
+ module Stackify
2
+ class ErrorsCatcher
3
+ def initialize(app, &block)
4
+ @app = app
5
+ @block = block
6
+ end
7
+
8
+ def call(env)
9
+ @block.call env
10
+ @app.call env
11
+ rescue Exception => exception
12
+ Stackify.logger_client.log_exception(StackifiedError.new exception, binding)
13
+ raise exception
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module Stackify
2
+ class ScheduleTask
3
+
4
+ attr_reader :limit, :attempts, :action
5
+
6
+ def initialize properties={}, &action
7
+ @limit = properties[:limit] || nil
8
+ @attempts = properties[:attempts] || 3
9
+ @success_condition = properties[:success_condition] || lambda{ |_result| true }
10
+ @action = action
11
+ end
12
+
13
+ def execute!
14
+ @action.call
15
+ end
16
+
17
+ def success? result_of_task_execution
18
+ @success_condition.call result_of_task_execution
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,79 @@
1
+ require 'thread'
2
+ module Stackify
3
+
4
+ class Scheduler
5
+
6
+ attr_accessor :period
7
+ attr_reader :iterations
8
+
9
+ def initialize
10
+ @should_run = true
11
+ @next_invocation_time = Time.now
12
+ @period = 60.0
13
+ @iterations = 0
14
+ @attempts = 3
15
+ end
16
+
17
+ def setup(period, task)
18
+ @task = task
19
+ @period = period if period
20
+ @should_run = true
21
+ @iterations = 0
22
+ @attempts = @task.attempts if @task.attempts
23
+ now = Time.now
24
+ @next_invocation_time = (now + @period)
25
+ end
26
+
27
+ def run(period=nil, task)
28
+ setup(period, task)
29
+ while keep_running? do
30
+ sleep_time = schedule_next_invocation
31
+ sleep(sleep_time) if sleep_time > 0 && @iterations != 0
32
+ @task_result = run_task if keep_running?
33
+ if @task.success? @task_result
34
+ @iterations += 1
35
+ @attempts = @task.attempts || @attempts
36
+ else
37
+ @attempts -= 1
38
+ end
39
+ end
40
+ @task.success? @task_result
41
+ end
42
+
43
+ def schedule_next_invocation
44
+ now = Time.now
45
+ while @next_invocation_time <= now && @period > 0
46
+ @next_invocation_time += @period
47
+ end
48
+ @next_invocation_time - Time.now
49
+ end
50
+
51
+ def keep_running?
52
+ @should_run && limit_is_not_reached? && attemps_are_not_over?
53
+ end
54
+
55
+ def attemps_are_not_over?
56
+ @attempts.nil? || @attempts > 0
57
+ end
58
+
59
+ def limit_is_not_reached?
60
+ @task.limit.nil? || @iterations < @task.limit
61
+ end
62
+
63
+ def stop
64
+ @should_run = false
65
+ end
66
+
67
+ def task_result
68
+ @task_result
69
+ end
70
+
71
+ def run_task
72
+ begin
73
+ @task.execute!
74
+ rescue Exception => e
75
+ ::Stackify.log_internal_error 'Scheduler: ' + e.message + '' + e.backtrace.to_s
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,36 @@
1
+ module Stackify::Backtrace
2
+
3
+ ALL_TEXT_FROM_START_TO_FIRST_COLON_REGEXP = /\A([^:]+)/
4
+ NUMBER_BETWEEN_TWO_COLONS_REGEXP = /:(\d+):/
5
+ TEXT_AFTER_IN_BEFORE_END_REGEXP = /in\s`(\S+)'\z/
6
+
7
+ def self.line_number backtrace_str
8
+ backtrace_str[NUMBER_BETWEEN_TWO_COLONS_REGEXP, 1]
9
+ end
10
+
11
+ def self.method_name backtrace_str
12
+ return nil unless backtrace_str
13
+ backtrace_str[TEXT_AFTER_IN_BEFORE_END_REGEXP, 1]
14
+ end
15
+
16
+ def self.file_name backtrace_str
17
+ backtrace_str[ALL_TEXT_FROM_START_TO_FIRST_COLON_REGEXP, 1]
18
+ end
19
+
20
+ def self.stacktrace depth=5, backtrace
21
+ return nil unless backtrace
22
+ new_backtrace = []
23
+ backtrace.take(depth).each do |line|
24
+ new_backtrace << {
25
+ 'LineNum' => line_number(line),
26
+ 'Method' => method_name(line),
27
+ 'CodeFileName' => file_name(line)
28
+ }
29
+ end
30
+ new_backtrace
31
+ end
32
+
33
+ def self.backtrace_in_line backtrace
34
+ backtrace.join("\n")
35
+ end
36
+ end
@@ -0,0 +1,78 @@
1
+ module Stackify
2
+
3
+ class Configuration
4
+
5
+ attr_accessor :api_key, :app_name, :app_location, :env, :log_level, :flood_limit,
6
+ :queue_max_size, :logger, :send_interval, :with_proxy,
7
+ :proxy_host, :proxy_port, :proxy_user, :proxy_pass, :mode
8
+
9
+ attr_reader :errors
10
+
11
+ def initialize
12
+ @errors = []
13
+ @api_key = ''
14
+ @env = :production
15
+ @flood_limit = 100
16
+ @queue_max_size = 1000
17
+ @send_interval = 60
18
+ @with_proxy = false
19
+ @log_level = :info
20
+ @mode = MODES[:both]
21
+ @logger = if defined? Rails
22
+ Logger.new(File.join(Rails.root, 'log', 'stackify.log'))
23
+ else
24
+ Logger.new('stackify.log')
25
+ end
26
+ end
27
+
28
+ def is_valid?
29
+ @errors = []
30
+ validate_send_interval && validate_mode if validate_config_types
31
+ @errors.empty?
32
+ end
33
+
34
+ private
35
+ def validate_send_interval
36
+ return true if 60 <= @send_interval && @send_interval <= 60000
37
+ @errors << 'Send interval is not correct!'
38
+ end
39
+
40
+ def validate_config_types
41
+ validate_api_key &&
42
+ validate_flood_limit_queue_max_size_and_send_interval &&
43
+ validate_log_level &&
44
+ validate_mode_type
45
+ end
46
+
47
+ def validate_mode_type
48
+ return true if @mode.is_a? Symbol
49
+ @errors << 'Mode should be a Symbol'
50
+ end
51
+
52
+ def validate_api_key
53
+ return true if @api_key.is_a? String
54
+ @errors << 'API_KEY should be a String'
55
+ end
56
+
57
+ def validate_flood_limit_queue_max_size_and_send_interval
58
+ answer = true
59
+ { 'Flood limit' => @flood_limit, "Queue's max size" => @queue_max_size, 'Send interval' => @send_interval }.each_pair do |k, v|
60
+ unless v.is_a? Integer
61
+ answer = false
62
+ @errors << "#{k} should be an Integer"
63
+ end
64
+ end
65
+ answer
66
+ end
67
+
68
+ def validate_log_level
69
+ return true if [:debug, :warn, :info, :error, :fatal].include? @log_level
70
+ @errors << "Log's level should has one of these values: [:debug, :warn, :info, :error, :fatal]"
71
+ end
72
+
73
+ def validate_mode
74
+ return true if MODES.has_value? @mode
75
+ @errors << 'Mode should be one of these values: [:both, :logging, :metrics]'
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,27 @@
1
+ module Stackify::Utils
2
+
3
+ def self.current_minute
4
+ Time.now.utc.to_i/60
5
+ end
6
+
7
+ def self.rounded_current_time
8
+ t = Time.now.utc
9
+ t - t.sec
10
+ end
11
+
12
+ def self.is_mode_on? mode
13
+ Stackify.configuration.mode == Stackify::MODES[:both] || Stackify.configuration.mode == mode
14
+ end
15
+
16
+ def self.do_only_if_authorized_and_mode_is_on mode, &block
17
+ if Stackify.authorized?
18
+ if is_mode_on? mode
19
+ yield
20
+ else
21
+ Stackify.internal_log :warn, "#{caller[0]}: Skipped because mode - #{mode.to_s} is disabled at configuration"
22
+ end
23
+ else
24
+ Stackify.internal_log :warn, "#{caller[0]}: Skipped due to authorization failure"
25
+ end
26
+ end
27
+ end