prefab-cloud-ruby 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/.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
|