prefab-cloud-ruby 0.21.0 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a34b06515d401b7e73fd8b6adf77294187d94ea3a1d1909d159ebfd63b4046ad
4
- data.tar.gz: 6ba449742b1a75517b35a47151452dbdf94c0a8aef0e58a639ae5b2481262d43
3
+ metadata.gz: 2cd914702e6efb141278bbf12bf120342c0de3d788e8e6399a3baefd8f11a102
4
+ data.tar.gz: d7ba7ad4210bbf2f89005479e970f33b6486a67fbef125502c86455830381797
5
5
  SHA512:
6
- metadata.gz: 0a32e0ac965c54d634c579de66927d2982cca26d7098daaf3cdfd2dda88bf04bd33265c1e9f85e303d75fc9360f6df202cee256e59a91012b94f0adcf29252aa
7
- data.tar.gz: 2c37b2cfeef43f44d2dcbe8835ff8ea130393ad98dd9dd61328371972bf1818ab12d932f6bec319a52ec2423f9204762c6305ab3643023a3aa5ea4bfd18974bf
6
+ metadata.gz: a797285f8e5ecffc90b5861f87af50c5aae93f6dc3ba1803b79232823ee4ff2fa4fdf4017c04712aa89fa5f607496eb99376dbc6595bcfad69e2df2ae0aa0324
7
+ data.tar.gz: 965bc69143e55822b5a53d691eb50ee5a883b50e85dfe3ecf868ab58b947efc717b752d287d636636846418539c2ce602451b12b4a29f596a41a41be2839ef7e
@@ -22,7 +22,7 @@ jobs:
22
22
  runs-on: ubuntu-latest
23
23
  strategy:
24
24
  matrix:
25
- ruby-version: ['2.6', '2.7', '3.0']
25
+ ruby-version: ['2.6', '2.7', '3.0', '3.1']
26
26
 
27
27
  steps:
28
28
  - uses: actions/checkout@v3
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.4.1)
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
- # Create a flag that is on for 10% of traffic, the entire beta group and user:1
9
- @feature_flags.upsert(Prefab::FeatureFlag.new(feature: "MyFeature", pct: 0.1, whitelisted: ["betas", "user:1"]))
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
- # Use Flags By Themselves
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
- # A single user should get the same result each time
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://www.prefab.cloud/documentation/installation
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) 2022 Jeff Dwyer. See LICENSE.txt for
79
+ Copyright (c) 2023 Jeff Dwyer. See LICENSE.txt for
78
80
  further details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.21.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
- stub_for(service, opts[:timeout]).send(method, *params)
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
@@ -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,
@@ -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 }
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Prefab
4
- class InternalLogger < Logger
4
+ class InternalLogger < ::Logger
5
5
  def initialize(path, logger)
6
6
  @path = path
7
7
  @logger = logger
@@ -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
@@ -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
- path = get_loc_path(loc)
30
- path = "#{@prefix}#{@prefix && '.'}#{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
- closest_log_level_match = @config_client.get(BASE_KEY, :WARN)
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), nil)
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
@@ -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
- def initialize(
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