prefab-cloud-ruby 0.21.0 → 0.23.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 +4 -4
- data/.github/workflows/ruby.yml +1 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +9 -1
- data/README.md +11 -9
- data/VERSION +1 -1
- data/lib/prefab/cancellable_interceptor.rb +3 -3
- data/lib/prefab/client.rb +44 -7
- data/lib/prefab/config_client.rb +17 -16
- data/lib/prefab/config_loader.rb +1 -1
- data/lib/prefab/internal_logger.rb +1 -1
- data/lib/prefab/log_path_collector.rb +98 -0
- data/lib/prefab/logger_client.rb +35 -16
- data/lib/prefab/options.rb +22 -3
- data/lib/prefab/yaml_config_parser.rb +2 -2
- data/lib/prefab_pb.rb +21 -0
- data/lib/prefab_services_pb.rb +14 -0
- data/prefab-cloud-ruby.gemspec +8 -3
- data/test/test_client.rb +24 -0
- data/test/test_config_resolver.rb +0 -4
- data/test/test_helper.rb +17 -1
- data/test/test_log_path_collector.rb +56 -0
- data/test/test_logger.rb +253 -15
- data/test/test_options.rb +32 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2cd914702e6efb141278bbf12bf120342c0de3d788e8e6399a3baefd8f11a102
|
4
|
+
data.tar.gz: d7ba7ad4210bbf2f89005479e970f33b6486a67fbef125502c86455830381797
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a797285f8e5ecffc90b5861f87af50c5aae93f6dc3ba1803b79232823ee4ff2fa4fdf4017c04712aa89fa5f607496eb99376dbc6595bcfad69e2df2ae0aa0324
|
7
|
+
data.tar.gz: 965bc69143e55822b5a53d691eb50ee5a883b50e85dfe3ecf868ab58b947efc717b752d287d636636846418539c2ce602451b12b4a29f596a41a41be2839ef7e
|
data/.github/workflows/ruby.yml
CHANGED
data/Gemfile
CHANGED
@@ -6,6 +6,7 @@ gem 'googleapis-common-protos-types', platforms: :ruby
|
|
6
6
|
gem 'google-protobuf', platforms: :ruby
|
7
7
|
gem 'grpc', platforms: :ruby
|
8
8
|
gem 'ld-eventsource'
|
9
|
+
gem 'uuid'
|
9
10
|
|
10
11
|
group :development do
|
11
12
|
gem 'benchmark-ips'
|
@@ -20,4 +21,5 @@ group :test do
|
|
20
21
|
gem 'minitest'
|
21
22
|
gem 'minitest-focus'
|
22
23
|
gem 'minitest-reporters'
|
24
|
+
gem 'timecop'
|
23
25
|
end
|
data/Gemfile.lock
CHANGED
@@ -68,6 +68,8 @@ GEM
|
|
68
68
|
llhttp-ffi (0.3.1)
|
69
69
|
ffi-compiler (~> 1.0)
|
70
70
|
rake (~> 13.0)
|
71
|
+
macaddr (1.7.2)
|
72
|
+
systemu (~> 2.6.5)
|
71
73
|
mini_portile2 (2.8.0)
|
72
74
|
minitest (5.16.2)
|
73
75
|
minitest-focus (1.3.1)
|
@@ -92,7 +94,7 @@ GEM
|
|
92
94
|
psych (3.3.1)
|
93
95
|
public_suffix (4.0.6)
|
94
96
|
racc (1.6.1)
|
95
|
-
rack (3.0.
|
97
|
+
rack (3.0.6.1)
|
96
98
|
rake (13.0.6)
|
97
99
|
rchardet (1.8.0)
|
98
100
|
rdoc (6.3.3)
|
@@ -103,10 +105,14 @@ GEM
|
|
103
105
|
docile (~> 1.1)
|
104
106
|
simplecov-html (~> 0.11)
|
105
107
|
simplecov-html (0.12.3)
|
108
|
+
systemu (2.6.5)
|
106
109
|
thread_safe (0.3.6)
|
110
|
+
timecop (0.9.4)
|
107
111
|
unf (0.1.4)
|
108
112
|
unf_ext
|
109
113
|
unf_ext (0.0.8)
|
114
|
+
uuid (2.3.9)
|
115
|
+
macaddr (~> 1.0)
|
110
116
|
|
111
117
|
PLATFORMS
|
112
118
|
ruby
|
@@ -127,6 +133,8 @@ DEPENDENCIES
|
|
127
133
|
minitest-reporters
|
128
134
|
rdoc
|
129
135
|
simplecov
|
136
|
+
timecop
|
137
|
+
uuid
|
130
138
|
|
131
139
|
BUNDLED WITH
|
132
140
|
2.3.5
|
data/README.md
CHANGED
@@ -3,18 +3,20 @@ Ruby Client for Prefab FeatureFlags, Config as a Service: https://www.prefab.clo
|
|
3
3
|
|
4
4
|
```ruby
|
5
5
|
client = Prefab::Client.new
|
6
|
-
@feature_flags = client.feature_flag_client
|
7
6
|
|
8
|
-
|
9
|
-
|
7
|
+
lookup_key = "user-123"
|
8
|
+
identity_attributes = {
|
9
|
+
team_id: 432,
|
10
|
+
user_id: 123,
|
11
|
+
subscription_level: 'pro',
|
12
|
+
email: "alice@example.com"
|
13
|
+
}
|
10
14
|
|
11
|
-
|
12
|
-
puts @feature_flags.feature_is_on? "MyFeature" # returns yes 10 pct of the time
|
15
|
+
result = client.enabled? "my-first-feature-flag", lookup_key, identity_attributes
|
13
16
|
|
14
|
-
|
15
|
-
puts @feature_flags.feature_is_on? "MyFeature", "user:1123"
|
17
|
+
puts "my-first-feature-flag is: #{result} for #{lookup_key}"
|
16
18
|
```
|
17
|
-
See full documentation https://
|
19
|
+
See full documentation https://docs.prefab.cloud/docs/ruby-sdk/ruby
|
18
20
|
|
19
21
|
## Supports
|
20
22
|
|
@@ -74,5 +76,5 @@ REMOTE_BRANCH=main LOCAL_BRANCH=main bundle exec rake release
|
|
74
76
|
|
75
77
|
## Copyright
|
76
78
|
|
77
|
-
Copyright (c)
|
79
|
+
Copyright (c) 2023 Jeff Dwyer. See LICENSE.txt for
|
78
80
|
further details.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.23.0
|
@@ -13,16 +13,16 @@ module Prefab
|
|
13
13
|
i = 0
|
14
14
|
while i < WAIT_SEC
|
15
15
|
if @call.instance_variable_get('@wrapped').cancelled?
|
16
|
-
@base_client.log_internal Logger::DEBUG, 'Cancelled streaming.'
|
16
|
+
@base_client.log_internal ::Logger::DEBUG, 'Cancelled streaming.'
|
17
17
|
return
|
18
18
|
else
|
19
|
-
@base_client.log_internal Logger::DEBUG, 'Unable to cancel streaming. Trying again'
|
19
|
+
@base_client.log_internal ::Logger::DEBUG, 'Unable to cancel streaming. Trying again'
|
20
20
|
@call.instance_variable_get('@wrapped').instance_variable_get('@call').cancel
|
21
21
|
i += 1
|
22
22
|
sleep(1)
|
23
23
|
end
|
24
24
|
end
|
25
|
-
@base_client.log_internal Logger::INFO, 'Unable to cancel streaming.'
|
25
|
+
@base_client.log_internal ::Logger::INFO, 'Unable to cancel streaming.'
|
26
26
|
end
|
27
27
|
|
28
28
|
def request_response(request:, call:, method:, metadata:, &block)
|
data/lib/prefab/client.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'uuid'
|
4
|
+
|
3
5
|
module Prefab
|
4
6
|
class Client
|
5
7
|
MAX_SLEEP_SEC = 10
|
@@ -13,16 +15,18 @@ module Prefab
|
|
13
15
|
attr_reader :api_key
|
14
16
|
attr_reader :prefab_api_url
|
15
17
|
attr_reader :options
|
18
|
+
attr_reader :instance_hash
|
16
19
|
|
17
20
|
def initialize(options = Prefab::Options.new)
|
18
|
-
@options = options
|
21
|
+
@options = options.is_a?(Prefab::Options) ? options : Prefab::Options.new(options)
|
19
22
|
@shared_cache = @options.shared_cache
|
20
23
|
@stats = @options.stats
|
21
24
|
@namespace = @options.namespace
|
22
25
|
@stubs = {}
|
26
|
+
@instance_hash = UUID.new.generate
|
23
27
|
|
24
28
|
if @options.local_only?
|
25
|
-
log_internal Logger::INFO, 'Prefab Running in Local Mode'
|
29
|
+
log_internal ::Logger::INFO, 'Prefab Running in Local Mode'
|
26
30
|
else
|
27
31
|
@api_key = @options.api_key
|
28
32
|
raise Prefab::Errors::InvalidApiKeyError, @api_key if @api_key.nil? || @api_key.empty? || api_key.count('-') < 1
|
@@ -30,12 +34,24 @@ module Prefab
|
|
30
34
|
@interceptor = Prefab::AuthInterceptor.new(@api_key)
|
31
35
|
@prefab_api_url = @options.prefab_api_url
|
32
36
|
@prefab_grpc_url = @options.prefab_grpc_url
|
33
|
-
log_internal Logger::INFO,
|
37
|
+
log_internal ::Logger::INFO,
|
34
38
|
"Prefab Connecting to: #{@prefab_api_url} and #{@prefab_grpc_url} Secure: #{http_secure?}"
|
35
39
|
at_exit do
|
36
40
|
channel.destroy
|
37
41
|
end
|
38
42
|
end
|
43
|
+
# start config client
|
44
|
+
config_client
|
45
|
+
end
|
46
|
+
|
47
|
+
def with_log_context(lookup_key, properties)
|
48
|
+
Thread.current[:prefab_log_lookup_key] = lookup_key
|
49
|
+
Thread.current[:prefab_log_properties] = properties
|
50
|
+
|
51
|
+
yield
|
52
|
+
ensure
|
53
|
+
Thread.current[:prefab_log_lookup_key] = nil
|
54
|
+
Thread.current[:prefab_log_properties] = {}
|
39
55
|
end
|
40
56
|
|
41
57
|
def channel
|
@@ -55,9 +71,26 @@ module Prefab
|
|
55
71
|
@feature_flag_client ||= Prefab::FeatureFlagClient.new(self)
|
56
72
|
end
|
57
73
|
|
74
|
+
def log_path_collector
|
75
|
+
return nil if @options.collect_max_paths <= 0
|
76
|
+
|
77
|
+
@log_path_collector ||= LogPathCollector.new(client: self, max_paths: @options.collect_max_paths,
|
78
|
+
sync_interval: @options.collect_sync_interval)
|
79
|
+
end
|
80
|
+
|
58
81
|
def log
|
59
82
|
@logger_client ||= Prefab::LoggerClient.new(@options.logdev, formatter: @options.log_formatter,
|
60
|
-
prefix: @options.log_prefix
|
83
|
+
prefix: @options.log_prefix,
|
84
|
+
log_path_collector: log_path_collector)
|
85
|
+
end
|
86
|
+
|
87
|
+
def set_rails_loggers
|
88
|
+
Rails.logger = log
|
89
|
+
ActionView::Base.logger = log
|
90
|
+
ActionController::Base.logger = log
|
91
|
+
ActiveJob::Base.logger = log
|
92
|
+
ActiveRecord::Base.logger = log
|
93
|
+
ActiveStorage.logger = log
|
61
94
|
end
|
62
95
|
|
63
96
|
def log_internal(level, msg, path = nil)
|
@@ -65,6 +98,9 @@ module Prefab
|
|
65
98
|
end
|
66
99
|
|
67
100
|
def request(service, method, req_options: {}, params: {})
|
101
|
+
# Future-proofing since we previously bumped into a conflict with a service with a `send` method
|
102
|
+
raise ArgumentError, 'Cannot call public_send on an grpc service in Ruby' if method.to_s == 'public_send'
|
103
|
+
|
68
104
|
opts = { timeout: 10 }.merge(req_options)
|
69
105
|
|
70
106
|
attempts = 0
|
@@ -72,16 +108,17 @@ module Prefab
|
|
72
108
|
|
73
109
|
begin
|
74
110
|
attempts += 1
|
75
|
-
|
111
|
+
|
112
|
+
stub_for(service, opts[:timeout]).public_send(method, *params)
|
76
113
|
rescue StandardError => e
|
77
|
-
log_internal Logger::WARN, e
|
114
|
+
log_internal ::Logger::WARN, e
|
78
115
|
|
79
116
|
raise e if Time.now - start_time > opts[:timeout]
|
80
117
|
|
81
118
|
sleep_seconds = [BASE_SLEEP_SEC * (2**(attempts - 1)), MAX_SLEEP_SEC].min
|
82
119
|
sleep_seconds *= (0.5 * (1 + rand))
|
83
120
|
sleep_seconds = [BASE_SLEEP_SEC, sleep_seconds].max
|
84
|
-
log_internal Logger::INFO, "Sleep #{sleep_seconds} and Reset #{service} #{method}"
|
121
|
+
log_internal ::Logger::INFO, "Sleep #{sleep_seconds} and Reset #{service} #{method}"
|
85
122
|
sleep sleep_seconds
|
86
123
|
reset!
|
87
124
|
retry
|
data/lib/prefab/config_client.rb
CHANGED
@@ -10,7 +10,7 @@ module Prefab
|
|
10
10
|
def initialize(base_client, timeout)
|
11
11
|
@base_client = base_client
|
12
12
|
@options = base_client.options
|
13
|
-
@base_client.log_internal Logger::DEBUG, 'Initialize ConfigClient'
|
13
|
+
@base_client.log_internal ::Logger::DEBUG, 'Initialize ConfigClient'
|
14
14
|
@timeout = timeout
|
15
15
|
|
16
16
|
@stream_lock = Concurrent::ReadWriteLock.new
|
@@ -21,9 +21,9 @@ module Prefab
|
|
21
21
|
@config_resolver = Prefab::ConfigResolver.new(@base_client, @config_loader)
|
22
22
|
|
23
23
|
@initialization_lock = Concurrent::ReadWriteLock.new
|
24
|
-
@base_client.log_internal Logger::DEBUG, 'Initialize ConfigClient: AcquireWriteLock'
|
24
|
+
@base_client.log_internal ::Logger::DEBUG, 'Initialize ConfigClient: AcquireWriteLock'
|
25
25
|
@initialization_lock.acquire_write_lock
|
26
|
-
@base_client.log_internal Logger::DEBUG, 'Initialize ConfigClient: AcquiredWriteLock'
|
26
|
+
@base_client.log_internal ::Logger::DEBUG, 'Initialize ConfigClient: AcquiredWriteLock'
|
27
27
|
@initialized_future = Concurrent::Future.execute { @initialization_lock.acquire_read_lock }
|
28
28
|
|
29
29
|
@cancellable_interceptor = Prefab::CancellableInterceptor.new(@base_client)
|
@@ -74,6 +74,7 @@ module Prefab
|
|
74
74
|
|
75
75
|
def get(key, default = Prefab::Client::NO_DEFAULT_PROVIDED, properties = {}, lookup_key = nil)
|
76
76
|
value = _get(key, lookup_key, properties)
|
77
|
+
|
77
78
|
value ? Prefab::ConfigValueUnwrapper.unwrap(value, key, properties) : handle_default(key, default)
|
78
79
|
end
|
79
80
|
|
@@ -99,7 +100,7 @@ module Prefab
|
|
99
100
|
raise Prefab::Errors::InitializationTimeoutError.new(@options.initialization_timeout_sec, key)
|
100
101
|
end
|
101
102
|
|
102
|
-
@base_client.log_internal Logger::WARN,
|
103
|
+
@base_client.log_internal ::Logger::WARN,
|
103
104
|
"Couldn't Initialize In #{@options.initialization_timeout_sec}. Key #{key}. Returning what we have"
|
104
105
|
@initialization_lock.release_write_lock
|
105
106
|
|
@@ -119,13 +120,13 @@ module Prefab
|
|
119
120
|
|
120
121
|
return if success
|
121
122
|
|
122
|
-
@base_client.log_internal Logger::INFO, 'LoadCheckpoint: Fallback to GRPC API'
|
123
|
+
@base_client.log_internal ::Logger::INFO, 'LoadCheckpoint: Fallback to GRPC API'
|
123
124
|
|
124
125
|
success = load_checkpoint_from_grpc_api
|
125
126
|
|
126
127
|
return if success
|
127
128
|
|
128
|
-
@base_client.log_internal Logger::WARN, 'No success loading checkpoints'
|
129
|
+
@base_client.log_internal ::Logger::WARN, 'No success loading checkpoints'
|
129
130
|
end
|
130
131
|
|
131
132
|
def load_checkpoint_from_grpc_api
|
@@ -135,9 +136,9 @@ module Prefab
|
|
135
136
|
load_configs(resp, :remote_api_grpc)
|
136
137
|
true
|
137
138
|
rescue GRPC::Unauthenticated
|
138
|
-
@base_client.log_internal Logger::WARN, 'Unauthenticated'
|
139
|
+
@base_client.log_internal ::Logger::WARN, 'Unauthenticated'
|
139
140
|
rescue StandardError => e
|
140
|
-
@base_client.log_internal Logger::WARN, "Unexpected grpc_api problem loading checkpoint #{e}"
|
141
|
+
@base_client.log_internal ::Logger::WARN, "Unexpected grpc_api problem loading checkpoint #{e}"
|
141
142
|
false
|
142
143
|
end
|
143
144
|
|
@@ -162,11 +163,11 @@ module Prefab
|
|
162
163
|
load_configs(configs, source)
|
163
164
|
true
|
164
165
|
else
|
165
|
-
@base_client.log_internal Logger::INFO, "Checkpoint #{source} failed to load. Response #{resp.status}"
|
166
|
+
@base_client.log_internal ::Logger::INFO, "Checkpoint #{source} failed to load. Response #{resp.status}"
|
166
167
|
false
|
167
168
|
end
|
168
169
|
rescue StandardError => e
|
169
|
-
@base_client.log_internal Logger::WARN, "Unexpected #{source} problem loading checkpoint #{e} #{conn}"
|
170
|
+
@base_client.log_internal ::Logger::WARN, "Unexpected #{source} problem loading checkpoint #{e} #{conn}"
|
170
171
|
false
|
171
172
|
end
|
172
173
|
|
@@ -180,10 +181,10 @@ module Prefab
|
|
180
181
|
@config_loader.set(config, source)
|
181
182
|
end
|
182
183
|
if @config_loader.highwater_mark > starting_highwater_mark
|
183
|
-
@base_client.log_internal Logger::INFO,
|
184
|
+
@base_client.log_internal ::Logger::INFO,
|
184
185
|
"Found new checkpoint with highwater id #{@config_loader.highwater_mark} from #{source} in project #{project_id} environment: #{project_env_id} and namespace: '#{@namespace}'"
|
185
186
|
else
|
186
|
-
@base_client.log_internal Logger::DEBUG,
|
187
|
+
@base_client.log_internal ::Logger::DEBUG,
|
187
188
|
"Checkpoint with highwater id #{@config_loader.highwater_mark} from #{source}. No changes.", 'load_configs'
|
188
189
|
end
|
189
190
|
@base_client.stats.increment('prefab.config.checkpoint.load')
|
@@ -201,7 +202,7 @@ module Prefab
|
|
201
202
|
delta = @checkpoint_freq_secs - (Time.now - started_at)
|
202
203
|
sleep(delta) if delta > 0
|
203
204
|
rescue StandardError => e
|
204
|
-
@base_client.log_internal Logger::INFO, "Issue Checkpointing #{e.message}"
|
205
|
+
@base_client.log_internal ::Logger::INFO, "Issue Checkpointing #{e.message}"
|
205
206
|
end
|
206
207
|
end
|
207
208
|
end
|
@@ -209,10 +210,10 @@ module Prefab
|
|
209
210
|
def finish_init!(source)
|
210
211
|
return unless @initialization_lock.write_locked?
|
211
212
|
|
212
|
-
@base_client.log_internal Logger::INFO, "Unlocked Config via #{source}"
|
213
|
+
@base_client.log_internal ::Logger::INFO, "Unlocked Config via #{source}"
|
213
214
|
@initialization_lock.release_write_lock
|
214
215
|
@base_client.log.set_config_client(self)
|
215
|
-
@base_client.log_internal Logger::INFO, to_s
|
216
|
+
@base_client.log_internal ::Logger::INFO, to_s
|
216
217
|
end
|
217
218
|
|
218
219
|
def start_sse_streaming_connection_thread(start_at_id)
|
@@ -223,7 +224,7 @@ module Prefab
|
|
223
224
|
"Authorization": "Basic #{auth_string}"
|
224
225
|
}
|
225
226
|
url = "#{@base_client.prefab_api_url}/api/v1/sse/config"
|
226
|
-
@base_client.log_internal Logger::INFO, "SSE Streaming Connect to #{url} start_at #{start_at_id}"
|
227
|
+
@base_client.log_internal ::Logger::INFO, "SSE Streaming Connect to #{url} start_at #{start_at_id}"
|
227
228
|
@streaming_thread = SSE::Client.new(url,
|
228
229
|
headers: headers,
|
229
230
|
read_timeout: SSE_READ_TIMEOUT,
|
data/lib/prefab/config_loader.rb
CHANGED
@@ -29,7 +29,7 @@ module Prefab
|
|
29
29
|
@api_config.delete(config.key)
|
30
30
|
else
|
31
31
|
if @api_config[config.key]
|
32
|
-
@base_client.log_internal Logger::DEBUG,
|
32
|
+
@base_client.log_internal ::Logger::DEBUG,
|
33
33
|
"Replace #{config.key} with value from #{source} #{@api_config[config.key][:config].id} -> #{config.id}"
|
34
34
|
end
|
35
35
|
@api_config[config.key] = { source: source, config: config }
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class LogPathCollector
|
5
|
+
INCREMENT = ->(count) { (count || 0) + 1 }
|
6
|
+
|
7
|
+
SEVERITY_KEY = {
|
8
|
+
::Logger::DEBUG => 'debugs',
|
9
|
+
::Logger::INFO => 'infos',
|
10
|
+
::Logger::WARN => 'warns',
|
11
|
+
::Logger::ERROR => 'errors',
|
12
|
+
::Logger::FATAL => 'fatals'
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def initialize(client:, max_paths:, sync_interval:)
|
16
|
+
@max_paths = max_paths
|
17
|
+
@sync_interval = sync_interval
|
18
|
+
@client = client
|
19
|
+
@start_at = now
|
20
|
+
|
21
|
+
@pool = Concurrent::ThreadPoolExecutor.new(
|
22
|
+
fallback_policy: :discard,
|
23
|
+
max_queue: 5,
|
24
|
+
max_threads: 4,
|
25
|
+
min_threads: 1,
|
26
|
+
name: 'prefab-log-paths'
|
27
|
+
)
|
28
|
+
|
29
|
+
@paths = Concurrent::Map.new
|
30
|
+
|
31
|
+
start_periodic_sync
|
32
|
+
end
|
33
|
+
|
34
|
+
def push(path, severity)
|
35
|
+
return unless @paths.size < @max_paths
|
36
|
+
|
37
|
+
@paths.compute([path, severity], &INCREMENT)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def sync
|
43
|
+
return if @paths.size.zero?
|
44
|
+
|
45
|
+
log_internal "Syncing #{@paths.size} paths"
|
46
|
+
|
47
|
+
flush
|
48
|
+
end
|
49
|
+
|
50
|
+
def flush
|
51
|
+
to_ship = @paths.dup
|
52
|
+
@paths.clear
|
53
|
+
|
54
|
+
start_at_was = @start_at
|
55
|
+
@start_at = now
|
56
|
+
|
57
|
+
@pool.post do
|
58
|
+
log_internal "Uploading stats for #{to_ship.size} paths"
|
59
|
+
|
60
|
+
aggregate = Hash.new { |h, k| h[k] = Prefab::Logger.new }
|
61
|
+
|
62
|
+
to_ship.each do |(path, severity), count|
|
63
|
+
aggregate[path][SEVERITY_KEY[severity]] = count
|
64
|
+
aggregate[path]['logger_name'] = path
|
65
|
+
end
|
66
|
+
|
67
|
+
loggers = Prefab::Loggers.new(
|
68
|
+
loggers: aggregate.values,
|
69
|
+
start_at: start_at_was,
|
70
|
+
end_at: now,
|
71
|
+
instance_hash: @client.instance_hash,
|
72
|
+
namespace: @client.namespace
|
73
|
+
)
|
74
|
+
|
75
|
+
@client.request Prefab::LoggerReportingService, :send, req_options: {}, params: loggers
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def start_periodic_sync
|
80
|
+
Thread.new do
|
81
|
+
log_internal "Initialized log path collector instance_hash=#{@client.instance_hash} max_paths=#{@max_paths} sync_interval=#{@sync_interval}"
|
82
|
+
|
83
|
+
loop do
|
84
|
+
sleep @sync_interval
|
85
|
+
sync
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def log_internal(message)
|
91
|
+
@client.log.log_internal message, 'log_path_collector', nil, ::Logger::INFO
|
92
|
+
end
|
93
|
+
|
94
|
+
def now
|
95
|
+
(Time.now.utc.to_f * 1000).to_i
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/prefab/logger_client.rb
CHANGED
@@ -1,33 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'prefab/log_path_collector'
|
4
|
+
|
3
5
|
module Prefab
|
4
|
-
class LoggerClient < Logger
|
6
|
+
class LoggerClient < ::Logger
|
5
7
|
SEP = '.'
|
6
8
|
BASE_KEY = 'log-level'
|
7
9
|
UNKNOWN_PATH = 'unknown.'
|
8
10
|
INTERNAL_PREFIX = 'cloud.prefab.client'
|
9
11
|
|
10
12
|
LOG_LEVEL_LOOKUPS = {
|
11
|
-
Prefab::LogLevel::NOT_SET_LOG_LEVEL => Logger::DEBUG,
|
12
|
-
Prefab::LogLevel::TRACE => Logger::DEBUG,
|
13
|
-
Prefab::LogLevel::DEBUG => Logger::DEBUG,
|
14
|
-
Prefab::LogLevel::INFO => Logger::INFO,
|
15
|
-
Prefab::LogLevel::WARN => Logger::WARN,
|
16
|
-
Prefab::LogLevel::ERROR => Logger::ERROR,
|
17
|
-
Prefab::LogLevel::FATAL => Logger::FATAL
|
13
|
+
Prefab::LogLevel::NOT_SET_LOG_LEVEL => ::Logger::DEBUG,
|
14
|
+
Prefab::LogLevel::TRACE => ::Logger::DEBUG,
|
15
|
+
Prefab::LogLevel::DEBUG => ::Logger::DEBUG,
|
16
|
+
Prefab::LogLevel::INFO => ::Logger::INFO,
|
17
|
+
Prefab::LogLevel::WARN => ::Logger::WARN,
|
18
|
+
Prefab::LogLevel::ERROR => ::Logger::ERROR,
|
19
|
+
Prefab::LogLevel::FATAL => ::Logger::FATAL
|
18
20
|
}
|
19
21
|
|
20
|
-
def initialize(logdev, formatter: nil, prefix: nil)
|
22
|
+
def initialize(logdev, log_path_collector: nil, formatter: nil, prefix: nil)
|
21
23
|
super(logdev)
|
22
24
|
self.formatter = formatter
|
23
25
|
@config_client = BootstrappingConfigClient.new
|
24
26
|
@silences = Concurrent::Map.new(initial_capacity: 2)
|
25
|
-
@prefix = prefix
|
27
|
+
@prefix = "#{prefix}#{prefix && '.'}"
|
28
|
+
|
29
|
+
@log_path_collector = log_path_collector
|
26
30
|
end
|
27
31
|
|
28
32
|
def add(severity, message = nil, progname = nil, loc, &block)
|
29
|
-
|
30
|
-
path =
|
33
|
+
path_loc = get_loc_path(loc)
|
34
|
+
path = @prefix + path_loc
|
35
|
+
|
36
|
+
@log_path_collector&.push(path_loc, severity)
|
31
37
|
|
32
38
|
log(message, path, progname, severity, &block)
|
33
39
|
end
|
@@ -43,7 +49,8 @@ module Prefab
|
|
43
49
|
end
|
44
50
|
|
45
51
|
def log(message, path, progname, severity)
|
46
|
-
severity ||= Logger::UNKNOWN
|
52
|
+
severity ||= ::Logger::UNKNOWN
|
53
|
+
|
47
54
|
return true if @logdev.nil? || severity < level_of(path) || @silences[local_log_id]
|
48
55
|
|
49
56
|
progname = "#{path}: #{progname || @progname}"
|
@@ -124,14 +131,26 @@ module Prefab
|
|
124
131
|
|
125
132
|
private
|
126
133
|
|
134
|
+
NO_DEFAULT = nil
|
135
|
+
|
127
136
|
# Find the closest match to 'log_level.path' in config
|
128
137
|
def level_of(path)
|
129
|
-
|
138
|
+
properties = Thread.current[:prefab_log_properties] || {}
|
139
|
+
lookup_key = Thread.current[:prefab_log_lookup_key] || nil
|
140
|
+
|
141
|
+
closest_log_level_match = nil
|
142
|
+
|
130
143
|
path.split(SEP).each_with_object([BASE_KEY]) do |n, memo|
|
131
144
|
memo << n
|
132
|
-
val = @config_client.get(memo.join(SEP),
|
145
|
+
val = @config_client.get(memo.join(SEP), NO_DEFAULT, properties, lookup_key)
|
133
146
|
closest_log_level_match = val unless val.nil?
|
134
147
|
end
|
148
|
+
|
149
|
+
if closest_log_level_match.nil?
|
150
|
+
# get the top-level setting or default to WARN
|
151
|
+
closest_log_level_match = @config_client.get(BASE_KEY, :WARN, properties, lookup_key)
|
152
|
+
end
|
153
|
+
|
135
154
|
closest_log_level_match_int = Prefab::LogLevel.resolve(closest_log_level_match)
|
136
155
|
LOG_LEVEL_LOOKUPS[closest_log_level_match_int]
|
137
156
|
end
|
@@ -158,7 +177,7 @@ module Prefab
|
|
158
177
|
# StubConfigClient to be used while config client initializes
|
159
178
|
# since it may log
|
160
179
|
class BootstrappingConfigClient
|
161
|
-
def get(_key, default = nil)
|
180
|
+
def get(_key, default = nil, _properties = {}, _lookup_key = nil)
|
162
181
|
ENV['PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL'] ? ENV['PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL'].upcase.to_sym : default
|
163
182
|
end
|
164
183
|
end
|
data/lib/prefab/options.rb
CHANGED
@@ -17,6 +17,7 @@ module Prefab
|
|
17
17
|
attr_reader :prefab_config_override_dir
|
18
18
|
attr_reader :prefab_config_classpath_dir
|
19
19
|
attr_reader :prefab_envs
|
20
|
+
attr_reader :collect_sync_interval
|
20
21
|
|
21
22
|
DEFAULT_LOG_FORMATTER = proc { |severity, datetime, progname, msg|
|
22
23
|
"#{severity.ljust(5)} #{datetime}:#{' ' if progname}#{progname} #{msg}\n"
|
@@ -37,7 +38,10 @@ module Prefab
|
|
37
38
|
LOCAL_ONLY = 2
|
38
39
|
end
|
39
40
|
|
40
|
-
|
41
|
+
DEFAULT_MAX_PATHS = 1_000
|
42
|
+
DEFAULT_SYNC_INTERVAL = 60
|
43
|
+
|
44
|
+
private def init(
|
41
45
|
api_key: ENV['PREFAB_API_KEY'],
|
42
46
|
logdev: $stdout,
|
43
47
|
stats: NoopStats.new, # receives increment("prefab.limitcheck", {:tags=>["policy_group:page_view", "pass:true"]})
|
@@ -55,9 +59,11 @@ module Prefab
|
|
55
59
|
prefab_datasources: ENV['PREFAB_DATASOURCES'] == 'LOCAL_ONLY' ? DATASOURCES::LOCAL_ONLY : DATASOURCES::ALL,
|
56
60
|
prefab_config_override_dir: Dir.home,
|
57
61
|
prefab_config_classpath_dir: '.',
|
58
|
-
prefab_envs: ENV['PREFAB_ENVS'].nil? ? [] : ENV['PREFAB_ENVS'].split(',')
|
62
|
+
prefab_envs: ENV['PREFAB_ENVS'].nil? ? [] : ENV['PREFAB_ENVS'].split(','),
|
63
|
+
collect_logs: true,
|
64
|
+
collect_max_paths: DEFAULT_MAX_PATHS,
|
65
|
+
collect_sync_interval: DEFAULT_SYNC_INTERVAL
|
59
66
|
)
|
60
|
-
# debugger
|
61
67
|
@api_key = api_key
|
62
68
|
@logdev = logdev
|
63
69
|
@stats = stats
|
@@ -74,12 +80,25 @@ module Prefab
|
|
74
80
|
@prefab_config_classpath_dir = prefab_config_classpath_dir
|
75
81
|
@prefab_config_override_dir = prefab_config_override_dir
|
76
82
|
@prefab_envs = Array(prefab_envs)
|
83
|
+
@collect_logs = collect_logs
|
84
|
+
@collect_max_paths = collect_max_paths
|
85
|
+
@collect_sync_interval = collect_sync_interval
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize(options = {})
|
89
|
+
init(**options)
|
77
90
|
end
|
78
91
|
|
79
92
|
def local_only?
|
80
93
|
@prefab_datasources == DATASOURCES::LOCAL_ONLY
|
81
94
|
end
|
82
95
|
|
96
|
+
def collect_max_paths
|
97
|
+
return 0 if !@collect_logs || local_only?
|
98
|
+
|
99
|
+
@collect_max_paths
|
100
|
+
end
|
101
|
+
|
83
102
|
# https://api.prefab.cloud -> https://api-prefab-cloud.global.ssl.fastly.net
|
84
103
|
def url_for_api_cdn
|
85
104
|
ENV['PREFAB_CDN_URL'] || "#{@prefab_api_url.gsub(/\./, '-')}.global.ssl.fastly.net"
|
@@ -21,10 +21,10 @@ module Prefab
|
|
21
21
|
|
22
22
|
def load
|
23
23
|
if File.exist?(@file)
|
24
|
-
@client.log_internal Logger::INFO, "Load #{@file}"
|
24
|
+
@client.log_internal ::Logger::INFO, "Load #{@file}"
|
25
25
|
YAML.load_file(@file)
|
26
26
|
else
|
27
|
-
@client.log_internal Logger::INFO, "No file #{@file}"
|
27
|
+
@client.log_internal ::Logger::INFO, "No file #{@file}"
|
28
28
|
{}
|
29
29
|
end
|
30
30
|
end
|