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.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +30 -0
- data/LICENSE.txt +22 -0
- data/README.md +157 -0
- data/Rakefile +2 -0
- data/lib/generators/stackify/stackify_generator.rb +13 -0
- data/lib/generators/stackify/templates/stackify.rb +17 -0
- data/lib/stackify-api-ruby.rb +166 -0
- data/lib/stackify/authorization/authorizable.rb +61 -0
- data/lib/stackify/authorization/authorization_client.rb +31 -0
- data/lib/stackify/engine.rb +21 -0
- data/lib/stackify/env_details.rb +108 -0
- data/lib/stackify/error.rb +56 -0
- data/lib/stackify/errors_governor.rb +65 -0
- data/lib/stackify/http_client.rb +50 -0
- data/lib/stackify/logger_client.rb +71 -0
- data/lib/stackify/logger_proxy.rb +35 -0
- data/lib/stackify/logs_sender.rb +78 -0
- data/lib/stackify/metrics/metric.rb +68 -0
- data/lib/stackify/metrics/metric_aggregate.rb +52 -0
- data/lib/stackify/metrics/metrics.rb +88 -0
- data/lib/stackify/metrics/metrics_client.rb +238 -0
- data/lib/stackify/metrics/metrics_queue.rb +26 -0
- data/lib/stackify/metrics/metrics_sender.rb +32 -0
- data/lib/stackify/metrics/monitor.rb +34 -0
- data/lib/stackify/msgs_queue.rb +78 -0
- data/lib/stackify/rack/errors_catcher.rb +17 -0
- data/lib/stackify/schedule_task.rb +23 -0
- data/lib/stackify/scheduler.rb +79 -0
- data/lib/stackify/utils/backtrace.rb +36 -0
- data/lib/stackify/utils/configuration.rb +78 -0
- data/lib/stackify/utils/methods.rb +27 -0
- data/lib/stackify/utils/msg_object.rb +22 -0
- data/lib/stackify/version.rb +3 -0
- data/lib/stackify/workers/add_msg_worker.rb +9 -0
- data/lib/stackify/workers/auth_worker.rb +18 -0
- data/lib/stackify/workers/logs_sender_worker.rb +17 -0
- data/lib/stackify/workers/worker.rb +65 -0
- data/spec/spec_helper.rb +17 -0
- data/stackify-api-ruby.gemspec +25 -0
- 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
|