prefab-cloud-ruby 0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.envrc.sample +3 -0
- data/.github/workflows/ruby.yml +46 -0
- data/.gitmodules +3 -0
- data/.rubocop.yml +13 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +169 -0
- data/CODEOWNERS +1 -0
- data/Gemfile +26 -0
- data/Gemfile.lock +188 -0
- data/LICENSE.txt +20 -0
- data/README.md +94 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/bin/console +21 -0
- data/compile_protos.sh +18 -0
- data/lib/prefab/client.rb +153 -0
- data/lib/prefab/config_client.rb +292 -0
- data/lib/prefab/config_client_presenter.rb +18 -0
- data/lib/prefab/config_loader.rb +84 -0
- data/lib/prefab/config_resolver.rb +77 -0
- data/lib/prefab/config_value_unwrapper.rb +115 -0
- data/lib/prefab/config_value_wrapper.rb +18 -0
- data/lib/prefab/context.rb +179 -0
- data/lib/prefab/context_shape.rb +20 -0
- data/lib/prefab/context_shape_aggregator.rb +65 -0
- data/lib/prefab/criteria_evaluator.rb +136 -0
- data/lib/prefab/encryption.rb +65 -0
- data/lib/prefab/error.rb +6 -0
- data/lib/prefab/errors/env_var_parse_error.rb +11 -0
- data/lib/prefab/errors/initialization_timeout_error.rb +13 -0
- data/lib/prefab/errors/invalid_api_key_error.rb +19 -0
- data/lib/prefab/errors/missing_default_error.rb +13 -0
- data/lib/prefab/errors/missing_env_var_error.rb +11 -0
- data/lib/prefab/errors/uninitialized_error.rb +13 -0
- data/lib/prefab/evaluation.rb +52 -0
- data/lib/prefab/evaluation_summary_aggregator.rb +87 -0
- data/lib/prefab/example_contexts_aggregator.rb +78 -0
- data/lib/prefab/exponential_backoff.rb +21 -0
- data/lib/prefab/feature_flag_client.rb +42 -0
- data/lib/prefab/http_connection.rb +41 -0
- data/lib/prefab/internal_logger.rb +16 -0
- data/lib/prefab/local_config_parser.rb +151 -0
- data/lib/prefab/log_path_aggregator.rb +69 -0
- data/lib/prefab/logger_client.rb +264 -0
- data/lib/prefab/murmer3.rb +50 -0
- data/lib/prefab/options.rb +208 -0
- data/lib/prefab/periodic_sync.rb +69 -0
- data/lib/prefab/prefab.rb +56 -0
- data/lib/prefab/rate_limit_cache.rb +41 -0
- data/lib/prefab/resolved_config_presenter.rb +86 -0
- data/lib/prefab/time_helpers.rb +7 -0
- data/lib/prefab/weighted_value_resolver.rb +42 -0
- data/lib/prefab/yaml_config_parser.rb +34 -0
- data/lib/prefab-cloud-ruby.rb +57 -0
- data/lib/prefab_pb.rb +93 -0
- data/prefab-cloud-ruby.gemspec +155 -0
- data/test/.prefab.default.config.yaml +2 -0
- data/test/.prefab.unit_tests.config.yaml +28 -0
- data/test/integration_test.rb +150 -0
- data/test/integration_test_helpers.rb +151 -0
- data/test/support/common_helpers.rb +180 -0
- data/test/support/mock_base_client.rb +42 -0
- data/test/support/mock_config_client.rb +19 -0
- data/test/support/mock_config_loader.rb +1 -0
- data/test/test_client.rb +444 -0
- data/test/test_config_client.rb +109 -0
- data/test/test_config_loader.rb +117 -0
- data/test/test_config_resolver.rb +430 -0
- data/test/test_config_value_unwrapper.rb +224 -0
- data/test/test_config_value_wrapper.rb +42 -0
- data/test/test_context.rb +203 -0
- data/test/test_context_shape.rb +50 -0
- data/test/test_context_shape_aggregator.rb +147 -0
- data/test/test_criteria_evaluator.rb +726 -0
- data/test/test_encryption.rb +16 -0
- data/test/test_evaluation_summary_aggregator.rb +162 -0
- data/test/test_example_contexts_aggregator.rb +238 -0
- data/test/test_exponential_backoff.rb +18 -0
- data/test/test_feature_flag_client.rb +48 -0
- data/test/test_helper.rb +17 -0
- data/test/test_integration.rb +58 -0
- data/test/test_local_config_parser.rb +147 -0
- data/test/test_log_path_aggregator.rb +62 -0
- data/test/test_logger.rb +621 -0
- data/test/test_logger_initialization.rb +12 -0
- data/test/test_options.rb +75 -0
- data/test/test_prefab.rb +12 -0
- data/test/test_rate_limit_cache.rb +44 -0
- data/test/test_weighted_value_resolver.rb +71 -0
- metadata +337 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
# Records the result of evaluating a config's criteria and forensics for reporting
|
5
|
+
class Evaluation
|
6
|
+
attr_reader :value
|
7
|
+
|
8
|
+
def initialize(config:, value:, value_index:, config_row_index:, context:, resolver:)
|
9
|
+
@config = config
|
10
|
+
@value = value
|
11
|
+
@value_index = value_index
|
12
|
+
@config_row_index = config_row_index
|
13
|
+
@context = context
|
14
|
+
@resolver = resolver
|
15
|
+
end
|
16
|
+
|
17
|
+
def unwrapped_value
|
18
|
+
deepest_value.unwrap
|
19
|
+
end
|
20
|
+
|
21
|
+
def reportable_value
|
22
|
+
deepest_value.reportable_value
|
23
|
+
end
|
24
|
+
|
25
|
+
def report_and_return(evaluation_summary_aggregator)
|
26
|
+
report(evaluation_summary_aggregator)
|
27
|
+
|
28
|
+
unwrapped_value
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def report(evaluation_summary_aggregator)
|
34
|
+
return if @config.config_type == :LOG_LEVEL
|
35
|
+
evaluation_summary_aggregator&.record(
|
36
|
+
config_key: @config.key,
|
37
|
+
config_type: @config.config_type,
|
38
|
+
counter: {
|
39
|
+
config_id: @config.id,
|
40
|
+
config_row_index: @config_row_index,
|
41
|
+
conditional_value_index: @value_index,
|
42
|
+
selected_value: deepest_value.reportable_wrapped_value,
|
43
|
+
weighted_value_index: deepest_value.weighted_value_index,
|
44
|
+
selected_index: nil # TODO
|
45
|
+
})
|
46
|
+
end
|
47
|
+
|
48
|
+
def deepest_value
|
49
|
+
@deepest_value ||= Prefab::ConfigValueUnwrapper.deepest_value(@value, @config, @context, @resolver)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'periodic_sync'
|
4
|
+
|
5
|
+
module Prefab
|
6
|
+
# This class aggregates the number of times each config is evaluated, and
|
7
|
+
# details about how the config is evaluated This data is reported to the
|
8
|
+
# server at a regular interval defined by `sync_interval`.
|
9
|
+
class EvaluationSummaryAggregator
|
10
|
+
include Prefab::PeriodicSync
|
11
|
+
|
12
|
+
LOG = Prefab::InternalLogger.new(EvaluationSummaryAggregator)
|
13
|
+
|
14
|
+
attr_reader :data
|
15
|
+
|
16
|
+
def initialize(client:, max_keys:, sync_interval:)
|
17
|
+
@client = client
|
18
|
+
@max_keys = max_keys
|
19
|
+
@name = 'evaluation_summary_aggregator'
|
20
|
+
|
21
|
+
@data = Concurrent::Hash.new
|
22
|
+
|
23
|
+
start_periodic_sync(sync_interval)
|
24
|
+
end
|
25
|
+
|
26
|
+
def record(config_key:, config_type:, counter:)
|
27
|
+
return if @data.size >= @max_keys
|
28
|
+
|
29
|
+
key = [config_key, config_type]
|
30
|
+
@data[key] ||= Concurrent::Hash.new
|
31
|
+
|
32
|
+
@data[key][counter] ||= 0
|
33
|
+
@data[key][counter] += 1
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def counter_proto(counter, count)
|
39
|
+
PrefabProto::ConfigEvaluationCounter.new(
|
40
|
+
config_id: counter[:config_id],
|
41
|
+
selected_index: counter[:selected_index],
|
42
|
+
config_row_index: counter[:config_row_index],
|
43
|
+
conditional_value_index: counter[:conditional_value_index],
|
44
|
+
weighted_value_index: counter[:weighted_value_index],
|
45
|
+
selected_value: counter[:selected_value],
|
46
|
+
count: count
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def flush(to_ship, start_at_was)
|
51
|
+
pool.post do
|
52
|
+
LOG.debug "Flushing #{to_ship.size} summaries"
|
53
|
+
|
54
|
+
summaries_proto = PrefabProto::ConfigEvaluationSummaries.new(
|
55
|
+
start: start_at_was,
|
56
|
+
end: Prefab::TimeHelpers.now_in_ms,
|
57
|
+
summaries: summaries(to_ship)
|
58
|
+
)
|
59
|
+
|
60
|
+
result = post('/api/v1/telemetry', events(summaries_proto))
|
61
|
+
|
62
|
+
LOG.debug "Uploaded #{to_ship.size} summaries: #{result.status}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def events(summaries)
|
67
|
+
event = PrefabProto::TelemetryEvent.new(summaries: summaries)
|
68
|
+
|
69
|
+
PrefabProto::TelemetryEvents.new(
|
70
|
+
instance_hash: @client.instance_hash,
|
71
|
+
events: [event]
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def summaries(data)
|
76
|
+
data.map do |(config_key, config_type), counters|
|
77
|
+
counter_protos = counters.map { |counter, count| counter_proto(counter, count) }
|
78
|
+
|
79
|
+
PrefabProto::ConfigEvaluationSummary.new(
|
80
|
+
key: config_key,
|
81
|
+
type: config_type,
|
82
|
+
counters: counter_protos
|
83
|
+
)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'periodic_sync'
|
4
|
+
|
5
|
+
module Prefab
|
6
|
+
# This class aggregates example contexts. It dedupes based on the
|
7
|
+
# concatenation of the keys of the contexts.
|
8
|
+
#
|
9
|
+
# It shouldn't send the same context more than once per hour.
|
10
|
+
class ExampleContextsAggregator
|
11
|
+
include Prefab::PeriodicSync
|
12
|
+
|
13
|
+
LOG = Prefab::InternalLogger.new(ExampleContextsAggregator)
|
14
|
+
|
15
|
+
attr_reader :data, :cache
|
16
|
+
|
17
|
+
ONE_HOUR = 60 * 60
|
18
|
+
|
19
|
+
def initialize(client:, max_contexts:, sync_interval:)
|
20
|
+
@client = client
|
21
|
+
@max_contexts = max_contexts
|
22
|
+
@name = 'example_contexts_aggregator'
|
23
|
+
|
24
|
+
@data = Concurrent::Array.new
|
25
|
+
@cache = Prefab::RateLimitCache.new(ONE_HOUR)
|
26
|
+
|
27
|
+
start_periodic_sync(sync_interval)
|
28
|
+
end
|
29
|
+
|
30
|
+
def record(contexts)
|
31
|
+
key = contexts.grouped_key
|
32
|
+
|
33
|
+
return unless @data.size < @max_contexts && !@cache.fresh?(key)
|
34
|
+
|
35
|
+
@cache.set(key)
|
36
|
+
|
37
|
+
@data.push(contexts)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def on_prepare_data
|
43
|
+
@cache.prune
|
44
|
+
end
|
45
|
+
|
46
|
+
def flush(to_ship, _)
|
47
|
+
pool.post do
|
48
|
+
LOG.debug "Flushing #{to_ship.size} examples"
|
49
|
+
|
50
|
+
result = post('/api/v1/telemetry', events(to_ship))
|
51
|
+
|
52
|
+
LOG.debug "Uploaded #{to_ship.size} examples: #{result.status}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def example_contexts(to_ship)
|
57
|
+
to_ship.map do |contexts|
|
58
|
+
PrefabProto::ExampleContext.new(
|
59
|
+
timestamp: contexts.seen_at * 1000,
|
60
|
+
contextSet: contexts.slim_proto
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def events(to_ship)
|
66
|
+
event = PrefabProto::TelemetryEvent.new(
|
67
|
+
example_contexts: PrefabProto::ExampleContexts.new(
|
68
|
+
examples: example_contexts(to_ship)
|
69
|
+
)
|
70
|
+
)
|
71
|
+
|
72
|
+
PrefabProto::TelemetryEvents.new(
|
73
|
+
instance_hash: @client.instance_hash,
|
74
|
+
events: [event]
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
# This class implements exponential backoff with a maximum delay.
|
5
|
+
#
|
6
|
+
# This is the default sync interval for aggregators.
|
7
|
+
class ExponentialBackoff
|
8
|
+
def initialize(max_delay:, initial_delay: 2, multiplier: 2)
|
9
|
+
@initial_delay = initial_delay
|
10
|
+
@max_delay = max_delay
|
11
|
+
@multiplier = multiplier
|
12
|
+
@delay = initial_delay
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
delay = @delay
|
17
|
+
@delay = [@delay * @multiplier, @max_delay].min
|
18
|
+
delay
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class FeatureFlagClient
|
5
|
+
def initialize(base_client)
|
6
|
+
@base_client = base_client
|
7
|
+
end
|
8
|
+
|
9
|
+
def feature_is_on?(feature_name)
|
10
|
+
feature_is_on_for?(feature_name, {})
|
11
|
+
end
|
12
|
+
|
13
|
+
def feature_is_on_for?(feature_name, properties)
|
14
|
+
variant = @base_client.config_client.get(feature_name, false, properties)
|
15
|
+
|
16
|
+
is_on?(variant)
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(feature_name, properties, default: false)
|
20
|
+
value = _get(feature_name, properties)
|
21
|
+
|
22
|
+
value.nil? ? default : value
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def _get(feature_name, properties)
|
28
|
+
@base_client.config_client.get(feature_name, nil, properties)
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_on?(variant)
|
32
|
+
return false if variant.nil?
|
33
|
+
|
34
|
+
return variant if variant == !!variant
|
35
|
+
|
36
|
+
variant.bool
|
37
|
+
rescue StandardError
|
38
|
+
@base_client.log.info("is_on? methods only work for boolean feature flags variants. This feature flags variant is '#{variant}'. Returning false")
|
39
|
+
false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class HttpConnection
|
5
|
+
AUTH_USER = 'authuser'
|
6
|
+
PROTO_HEADERS = {
|
7
|
+
'Content-Type' => 'application/x-protobuf',
|
8
|
+
'Accept' => 'application/x-protobuf',
|
9
|
+
'X-PrefabCloud-Client-Version' => "prefab-cloud-ruby-#{Prefab::VERSION}"
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
def initialize(api_root, api_key)
|
13
|
+
@api_root = api_root
|
14
|
+
@api_key = api_key
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(path)
|
18
|
+
connection(PROTO_HEADERS).get(path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def post(path, body)
|
22
|
+
connection(PROTO_HEADERS).post(path, body.to_proto)
|
23
|
+
end
|
24
|
+
|
25
|
+
def connection(headers = {})
|
26
|
+
if Faraday::VERSION[0].to_i >= 2
|
27
|
+
Faraday.new(@api_root) do |conn|
|
28
|
+
conn.request :authorization, :basic, AUTH_USER, @api_key
|
29
|
+
|
30
|
+
conn.headers.merge!(headers)
|
31
|
+
end
|
32
|
+
else
|
33
|
+
Faraday.new(@api_root) do |conn|
|
34
|
+
conn.request :basic_auth, AUTH_USER, @api_key
|
35
|
+
|
36
|
+
conn.headers.merge!(headers)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class InternalLogger < StaticLogger
|
5
|
+
INTERNAL_PREFIX = 'cloud.prefab.client'
|
6
|
+
|
7
|
+
def initialize(path)
|
8
|
+
if path.is_a?(Class)
|
9
|
+
path_string = path.name.split('::').last.downcase
|
10
|
+
else
|
11
|
+
path_string = path
|
12
|
+
end
|
13
|
+
super("#{INTERNAL_PREFIX}.#{path_string}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class LocalConfigParser
|
5
|
+
class << self
|
6
|
+
def parse(key, value, config, file)
|
7
|
+
if value.instance_of?(Hash)
|
8
|
+
if value['feature_flag']
|
9
|
+
config[key] = feature_flag_config(file, key, value)
|
10
|
+
elsif value['type'] == 'provided'
|
11
|
+
config[key] = provided_config(file, key, value)
|
12
|
+
elsif value['decrypt_with'] || value['confidential']
|
13
|
+
config[key] = complex_string(file, key, value)
|
14
|
+
else
|
15
|
+
value.each do |nest_key, nest_value|
|
16
|
+
nested_key = "#{key}.#{nest_key}"
|
17
|
+
nested_key = key if nest_key == '_'
|
18
|
+
parse(nested_key, nest_value, config, file)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
else
|
22
|
+
config[key] = {
|
23
|
+
source: file,
|
24
|
+
match: 'default',
|
25
|
+
config: PrefabProto::Config.new(
|
26
|
+
config_type: :CONFIG,
|
27
|
+
key: key,
|
28
|
+
rows: [
|
29
|
+
PrefabProto::ConfigRow.new(values: [
|
30
|
+
PrefabProto::ConditionalValue.new(value: value_from(key, value))
|
31
|
+
])
|
32
|
+
]
|
33
|
+
)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
config
|
38
|
+
end
|
39
|
+
|
40
|
+
def value_from(key, raw)
|
41
|
+
case raw
|
42
|
+
when String
|
43
|
+
if key.to_s.start_with? Prefab::LoggerClient::BASE_KEY
|
44
|
+
prefab_log_level_resolve = PrefabProto::LogLevel.resolve(raw.upcase.to_sym) || PrefabProto::LogLevel::NOT_SET_LOG_LEVEL
|
45
|
+
{ log_level: prefab_log_level_resolve }
|
46
|
+
else
|
47
|
+
{ string: raw }
|
48
|
+
end
|
49
|
+
when Integer
|
50
|
+
{ int: raw }
|
51
|
+
when TrueClass, FalseClass
|
52
|
+
{ bool: raw }
|
53
|
+
when Float
|
54
|
+
{ double: raw }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def feature_flag_config(file, key, value)
|
59
|
+
criterion = (parse_criterion(value['criterion']) if value['criterion'])
|
60
|
+
|
61
|
+
variant = PrefabProto::ConfigValue.new(value_from(key, value['value']))
|
62
|
+
|
63
|
+
row = PrefabProto::ConfigRow.new(
|
64
|
+
values: [
|
65
|
+
PrefabProto::ConditionalValue.new(
|
66
|
+
criteria: [criterion].compact,
|
67
|
+
value: variant
|
68
|
+
)
|
69
|
+
]
|
70
|
+
)
|
71
|
+
|
72
|
+
raise Prefab::Error, "Feature flag config `#{key}` #{file} must have a `value`" unless value.key?('value')
|
73
|
+
|
74
|
+
{
|
75
|
+
source: file,
|
76
|
+
match: key,
|
77
|
+
config: PrefabProto::Config.new(
|
78
|
+
config_type: :FEATURE_FLAG,
|
79
|
+
key: key,
|
80
|
+
allowable_values: [variant],
|
81
|
+
rows: [row]
|
82
|
+
)
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def provided_config(file, key, value_hash)
|
87
|
+
value = PrefabProto::ConfigValue.new(provided: PrefabProto::Provided.new(
|
88
|
+
source: :ENV_VAR,
|
89
|
+
lookup: value_hash["lookup"],
|
90
|
+
),
|
91
|
+
confidential: value_hash["confidential"],
|
92
|
+
)
|
93
|
+
|
94
|
+
row = PrefabProto::ConfigRow.new(
|
95
|
+
values: [
|
96
|
+
PrefabProto::ConditionalValue.new(
|
97
|
+
value: value
|
98
|
+
)
|
99
|
+
]
|
100
|
+
)
|
101
|
+
|
102
|
+
{
|
103
|
+
source: file,
|
104
|
+
match: value.provided.lookup,
|
105
|
+
config: PrefabProto::Config.new(
|
106
|
+
config_type: :CONFIG,
|
107
|
+
key: key,
|
108
|
+
rows: [row]
|
109
|
+
)
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
def complex_string(file, key, value_hash)
|
114
|
+
value = PrefabProto::ConfigValue.new(
|
115
|
+
string: value_hash["value"],
|
116
|
+
confidential: value_hash["confidential"],
|
117
|
+
decrypt_with: value_hash["decrypt_with"],
|
118
|
+
)
|
119
|
+
|
120
|
+
row = PrefabProto::ConfigRow.new(
|
121
|
+
values: [
|
122
|
+
PrefabProto::ConditionalValue.new(
|
123
|
+
value: value
|
124
|
+
)
|
125
|
+
]
|
126
|
+
)
|
127
|
+
|
128
|
+
{
|
129
|
+
source: file,
|
130
|
+
config: PrefabProto::Config.new(
|
131
|
+
config_type: :CONFIG,
|
132
|
+
key: key,
|
133
|
+
rows: [row]
|
134
|
+
)
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
138
|
+
def parse_criterion(criterion)
|
139
|
+
PrefabProto::Criterion.new(operator: criterion['operator'],
|
140
|
+
property_name: criterion['property'],
|
141
|
+
value_to_match: parse_value_to_match(criterion['values']))
|
142
|
+
end
|
143
|
+
|
144
|
+
def parse_value_to_match(values)
|
145
|
+
raise "Can't handle #{values}" unless values.instance_of?(Array)
|
146
|
+
|
147
|
+
PrefabProto::ConfigValue.new(string_list: PrefabProto::StringList.new(values: values))
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'periodic_sync'
|
4
|
+
|
5
|
+
module Prefab
|
6
|
+
class LogPathAggregator
|
7
|
+
LOG = Prefab::InternalLogger.new(LogPathAggregator)
|
8
|
+
|
9
|
+
include Prefab::PeriodicSync
|
10
|
+
|
11
|
+
INCREMENT = ->(count) { (count || 0) + 1 }
|
12
|
+
|
13
|
+
SEVERITY_KEY = {
|
14
|
+
::Logger::DEBUG => 'debugs',
|
15
|
+
::Logger::INFO => 'infos',
|
16
|
+
::Logger::WARN => 'warns',
|
17
|
+
::Logger::ERROR => 'errors',
|
18
|
+
::Logger::FATAL => 'fatals'
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
attr_reader :data
|
22
|
+
|
23
|
+
def initialize(client:, max_paths:, sync_interval:)
|
24
|
+
@max_paths = max_paths
|
25
|
+
@client = client
|
26
|
+
@name = 'log_path_aggregator'
|
27
|
+
|
28
|
+
@data = Concurrent::Map.new
|
29
|
+
|
30
|
+
@last_data_sent = nil
|
31
|
+
@last_request = nil
|
32
|
+
|
33
|
+
start_periodic_sync(sync_interval)
|
34
|
+
end
|
35
|
+
|
36
|
+
def push(path, severity)
|
37
|
+
return if @data.size >= @max_paths
|
38
|
+
|
39
|
+
@data.compute([path, severity], &INCREMENT)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def flush(to_ship, start_at_was)
|
45
|
+
pool.post do
|
46
|
+
LOG.debug "Uploading stats for #{to_ship.size} paths"
|
47
|
+
|
48
|
+
aggregate = Hash.new { |h, k| h[k] = PrefabProto::Logger.new }
|
49
|
+
|
50
|
+
to_ship.each do |(path, severity), count|
|
51
|
+
aggregate[path][SEVERITY_KEY[severity]] = count
|
52
|
+
aggregate[path]['logger_name'] = path
|
53
|
+
end
|
54
|
+
|
55
|
+
loggers = PrefabProto::Loggers.new(
|
56
|
+
loggers: aggregate.values,
|
57
|
+
start_at: start_at_was,
|
58
|
+
end_at: Prefab::TimeHelpers.now_in_ms,
|
59
|
+
instance_hash: @client.instance_hash,
|
60
|
+
namespace: @client.namespace
|
61
|
+
)
|
62
|
+
|
63
|
+
result = post('/api/v1/known-loggers', loggers)
|
64
|
+
|
65
|
+
LOG.debug "Uploaded #{to_ship.size} paths: #{result.status}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|