prefab-cloud-ruby 0.21.0 → 0.22.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a34b06515d401b7e73fd8b6adf77294187d94ea3a1d1909d159ebfd63b4046ad
4
- data.tar.gz: 6ba449742b1a75517b35a47151452dbdf94c0a8aef0e58a639ae5b2481262d43
3
+ metadata.gz: f5df09f0991b0d46c29ce33bc010116733960a5004ede7dd5a0df868b15e4a29
4
+ data.tar.gz: ded97aa4f8e2dc37e7581ee5d29c7bcb3eff88e7aa8f72fedb7bd9710050bfec
5
5
  SHA512:
6
- metadata.gz: 0a32e0ac965c54d634c579de66927d2982cca26d7098daaf3cdfd2dda88bf04bd33265c1e9f85e303d75fc9360f6df202cee256e59a91012b94f0adcf29252aa
7
- data.tar.gz: 2c37b2cfeef43f44d2dcbe8835ff8ea130393ad98dd9dd61328371972bf1818ab12d932f6bec319a52ec2423f9204762c6305ab3643023a3aa5ea4bfd18974bf
6
+ metadata.gz: 5e44b86fabd88830aed86910f4ff9175df14a06dc30b19821c741c691a9637bcff69870de0d816c2348a650dc9eb5b3a3c54a4452f71039aaab2f042086c3536
7
+ data.tar.gz: d81cf8af71e9aa189e7ba8b7078af6472df4dc6e3b08a33771163363424986d8b194c0ed88fa961217758ee9e5d21ca5604810f96db53a80366d72df606cb6d7
@@ -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)
@@ -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.22.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,7 +34,7 @@ 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
@@ -55,9 +59,17 @@ module Prefab
55
59
  @feature_flag_client ||= Prefab::FeatureFlagClient.new(self)
56
60
  end
57
61
 
62
+ def log_path_collector
63
+ return nil if @options.collect_max_paths <= 0
64
+
65
+ @log_path_collector ||= LogPathCollector.new(client: self, max_paths: @options.collect_max_paths,
66
+ sync_interval: @options.collect_sync_interval)
67
+ end
68
+
58
69
  def log
59
70
  @logger_client ||= Prefab::LoggerClient.new(@options.logdev, formatter: @options.log_formatter,
60
- prefix: @options.log_prefix)
71
+ prefix: @options.log_prefix,
72
+ log_path_collector: log_path_collector)
61
73
  end
62
74
 
63
75
  def log_internal(level, msg, path = nil)
@@ -65,6 +77,9 @@ module Prefab
65
77
  end
66
78
 
67
79
  def request(service, method, req_options: {}, params: {})
80
+ # Future-proofing since we previously bumped into a conflict with a service with a `send` method
81
+ raise ArgumentError, 'Cannot call public_send on an grpc service in Ruby' if method.to_s == 'public_send'
82
+
68
83
  opts = { timeout: 10 }.merge(req_options)
69
84
 
70
85
  attempts = 0
@@ -72,16 +87,17 @@ module Prefab
72
87
 
73
88
  begin
74
89
  attempts += 1
75
- stub_for(service, opts[:timeout]).send(method, *params)
90
+
91
+ stub_for(service, opts[:timeout]).public_send(method, *params)
76
92
  rescue StandardError => e
77
- log_internal Logger::WARN, e
93
+ log_internal ::Logger::WARN, e
78
94
 
79
95
  raise e if Time.now - start_time > opts[:timeout]
80
96
 
81
97
  sleep_seconds = [BASE_SLEEP_SEC * (2**(attempts - 1)), MAX_SLEEP_SEC].min
82
98
  sleep_seconds *= (0.5 * (1 + rand))
83
99
  sleep_seconds = [BASE_SLEEP_SEC, sleep_seconds].max
84
- log_internal Logger::INFO, "Sleep #{sleep_seconds} and Reset #{service} #{method}"
100
+ log_internal ::Logger::INFO, "Sleep #{sleep_seconds} and Reset #{service} #{method}"
85
101
  sleep sleep_seconds
86
102
  reset!
87
103
  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)
@@ -99,7 +99,7 @@ module Prefab
99
99
  raise Prefab::Errors::InitializationTimeoutError.new(@options.initialization_timeout_sec, key)
100
100
  end
101
101
 
102
- @base_client.log_internal Logger::WARN,
102
+ @base_client.log_internal ::Logger::WARN,
103
103
  "Couldn't Initialize In #{@options.initialization_timeout_sec}. Key #{key}. Returning what we have"
104
104
  @initialization_lock.release_write_lock
105
105
 
@@ -119,13 +119,13 @@ module Prefab
119
119
 
120
120
  return if success
121
121
 
122
- @base_client.log_internal Logger::INFO, 'LoadCheckpoint: Fallback to GRPC API'
122
+ @base_client.log_internal ::Logger::INFO, 'LoadCheckpoint: Fallback to GRPC API'
123
123
 
124
124
  success = load_checkpoint_from_grpc_api
125
125
 
126
126
  return if success
127
127
 
128
- @base_client.log_internal Logger::WARN, 'No success loading checkpoints'
128
+ @base_client.log_internal ::Logger::WARN, 'No success loading checkpoints'
129
129
  end
130
130
 
131
131
  def load_checkpoint_from_grpc_api
@@ -135,9 +135,9 @@ module Prefab
135
135
  load_configs(resp, :remote_api_grpc)
136
136
  true
137
137
  rescue GRPC::Unauthenticated
138
- @base_client.log_internal Logger::WARN, 'Unauthenticated'
138
+ @base_client.log_internal ::Logger::WARN, 'Unauthenticated'
139
139
  rescue StandardError => e
140
- @base_client.log_internal Logger::WARN, "Unexpected grpc_api problem loading checkpoint #{e}"
140
+ @base_client.log_internal ::Logger::WARN, "Unexpected grpc_api problem loading checkpoint #{e}"
141
141
  false
142
142
  end
143
143
 
@@ -162,11 +162,11 @@ module Prefab
162
162
  load_configs(configs, source)
163
163
  true
164
164
  else
165
- @base_client.log_internal Logger::INFO, "Checkpoint #{source} failed to load. Response #{resp.status}"
165
+ @base_client.log_internal ::Logger::INFO, "Checkpoint #{source} failed to load. Response #{resp.status}"
166
166
  false
167
167
  end
168
168
  rescue StandardError => e
169
- @base_client.log_internal Logger::WARN, "Unexpected #{source} problem loading checkpoint #{e} #{conn}"
169
+ @base_client.log_internal ::Logger::WARN, "Unexpected #{source} problem loading checkpoint #{e} #{conn}"
170
170
  false
171
171
  end
172
172
 
@@ -180,10 +180,10 @@ module Prefab
180
180
  @config_loader.set(config, source)
181
181
  end
182
182
  if @config_loader.highwater_mark > starting_highwater_mark
183
- @base_client.log_internal Logger::INFO,
183
+ @base_client.log_internal ::Logger::INFO,
184
184
  "Found new checkpoint with highwater id #{@config_loader.highwater_mark} from #{source} in project #{project_id} environment: #{project_env_id} and namespace: '#{@namespace}'"
185
185
  else
186
- @base_client.log_internal Logger::DEBUG,
186
+ @base_client.log_internal ::Logger::DEBUG,
187
187
  "Checkpoint with highwater id #{@config_loader.highwater_mark} from #{source}. No changes.", 'load_configs'
188
188
  end
189
189
  @base_client.stats.increment('prefab.config.checkpoint.load')
@@ -201,7 +201,7 @@ module Prefab
201
201
  delta = @checkpoint_freq_secs - (Time.now - started_at)
202
202
  sleep(delta) if delta > 0
203
203
  rescue StandardError => e
204
- @base_client.log_internal Logger::INFO, "Issue Checkpointing #{e.message}"
204
+ @base_client.log_internal ::Logger::INFO, "Issue Checkpointing #{e.message}"
205
205
  end
206
206
  end
207
207
  end
@@ -209,10 +209,10 @@ module Prefab
209
209
  def finish_init!(source)
210
210
  return unless @initialization_lock.write_locked?
211
211
 
212
- @base_client.log_internal Logger::INFO, "Unlocked Config via #{source}"
212
+ @base_client.log_internal ::Logger::INFO, "Unlocked Config via #{source}"
213
213
  @initialization_lock.release_write_lock
214
214
  @base_client.log.set_config_client(self)
215
- @base_client.log_internal Logger::INFO, to_s
215
+ @base_client.log_internal ::Logger::INFO, to_s
216
216
  end
217
217
 
218
218
  def start_sse_streaming_connection_thread(start_at_id)
@@ -223,7 +223,7 @@ module Prefab
223
223
  "Authorization": "Basic #{auth_string}"
224
224
  }
225
225
  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}"
226
+ @base_client.log_internal ::Logger::INFO, "SSE Streaming Connect to #{url} start_at #{start_at_id}"
227
227
  @streaming_thread = SSE::Client.new(url,
228
228
  headers: headers,
229
229
  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}"
@@ -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
data/lib/prefab_pb.rb CHANGED
@@ -181,6 +181,24 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
181
181
  optional :sequence_name, :string, 3
182
182
  optional :size, :int64, 4
183
183
  end
184
+ add_message "prefab.Loggers" do
185
+ repeated :loggers, :message, 1, "prefab.Logger"
186
+ optional :start_at, :int64, 2
187
+ optional :end_at, :int64, 3
188
+ optional :instance_hash, :string, 4
189
+ proto3_optional :namespace, :string, 5
190
+ end
191
+ add_message "prefab.Logger" do
192
+ optional :logger_name, :string, 1
193
+ proto3_optional :traces, :int64, 2
194
+ proto3_optional :debugs, :int64, 3
195
+ proto3_optional :infos, :int64, 4
196
+ proto3_optional :warns, :int64, 5
197
+ proto3_optional :errors, :int64, 6
198
+ proto3_optional :fatals, :int64, 7
199
+ end
200
+ add_message "prefab.LoggerReportResponse" do
201
+ end
184
202
  add_enum "prefab.ConfigType" do
185
203
  value :NOT_SET_CONFIG_TYPE, 0
186
204
  value :CONFIG, 1
@@ -236,6 +254,9 @@ module Prefab
236
254
  CreationResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.CreationResponse").msgclass
237
255
  IdBlock = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.IdBlock").msgclass
238
256
  IdBlockRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.IdBlockRequest").msgclass
257
+ Loggers = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.Loggers").msgclass
258
+ Logger = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.Logger").msgclass
259
+ LoggerReportResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.LoggerReportResponse").msgclass
239
260
  ConfigType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.ConfigType").enummodule
240
261
  LogLevel = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.LogLevel").enummodule
241
262
  OnFailure = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.OnFailure").enummodule
@@ -61,6 +61,20 @@ module Prefab
61
61
  rpc :GetAll, ::Prefab::Identity, ::Prefab::ConfigEvaluations
62
62
  end
63
63
 
64
+ Stub = Service.rpc_stub_class
65
+ end
66
+ module LoggerReportingService
67
+ class Service
68
+
69
+ include ::GRPC::GenericService
70
+
71
+ self.marshal_class_method = :encode
72
+ self.unmarshal_class_method = :decode
73
+ self.service_name = 'prefab.LoggerReportingService'
74
+
75
+ rpc :Send, ::Prefab::Loggers, ::Prefab::LoggerReportResponse
76
+ end
77
+
64
78
  Stub = Service.rpc_stub_class
65
79
  end
66
80
  end
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: prefab-cloud-ruby 0.21.0 ruby lib
5
+ # stub: prefab-cloud-ruby 0.22.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "prefab-cloud-ruby".freeze
9
- s.version = "0.21.0"
9
+ s.version = "0.22.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Jeff Dwyer".freeze]
14
- s.date = "2023-02-27"
14
+ s.date = "2023-03-15"
15
15
  s.description = "RateLimits & Config as a service".freeze
16
16
  s.email = "jdwyer@prefab.cloud".freeze
17
17
  s.extra_rdoc_files = [
@@ -48,6 +48,7 @@ Gem::Specification.new do |s|
48
48
  "lib/prefab/feature_flag_client.rb",
49
49
  "lib/prefab/internal_logger.rb",
50
50
  "lib/prefab/local_config_parser.rb",
51
+ "lib/prefab/log_path_collector.rb",
51
52
  "lib/prefab/logger_client.rb",
52
53
  "lib/prefab/murmer3.rb",
53
54
  "lib/prefab/noop_cache.rb",
@@ -74,7 +75,9 @@ Gem::Specification.new do |s|
74
75
  "test/test_helper.rb",
75
76
  "test/test_integration.rb",
76
77
  "test/test_local_config_parser.rb",
78
+ "test/test_log_path_collector.rb",
77
79
  "test/test_logger.rb",
80
+ "test/test_options.rb",
78
81
  "test/test_weighted_value_resolver.rb"
79
82
  ]
80
83
  s.homepage = "http://github.com/prefab-cloud/prefab-cloud-ruby".freeze
@@ -93,6 +96,7 @@ Gem::Specification.new do |s|
93
96
  s.add_runtime_dependency(%q<google-protobuf>.freeze, [">= 0"])
94
97
  s.add_runtime_dependency(%q<grpc>.freeze, [">= 0"])
95
98
  s.add_runtime_dependency(%q<ld-eventsource>.freeze, [">= 0"])
99
+ s.add_runtime_dependency(%q<uuid>.freeze, [">= 0"])
96
100
  s.add_development_dependency(%q<benchmark-ips>.freeze, [">= 0"])
97
101
  s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
98
102
  s.add_development_dependency(%q<grpc-tools>.freeze, [">= 0"])
@@ -106,6 +110,7 @@ Gem::Specification.new do |s|
106
110
  s.add_dependency(%q<google-protobuf>.freeze, [">= 0"])
107
111
  s.add_dependency(%q<grpc>.freeze, [">= 0"])
108
112
  s.add_dependency(%q<ld-eventsource>.freeze, [">= 0"])
113
+ s.add_dependency(%q<uuid>.freeze, [">= 0"])
109
114
  s.add_dependency(%q<benchmark-ips>.freeze, [">= 0"])
110
115
  s.add_dependency(%q<bundler>.freeze, [">= 0"])
111
116
  s.add_dependency(%q<grpc-tools>.freeze, [">= 0"])
data/test/test_client.rb CHANGED
@@ -84,6 +84,30 @@ class TestClient < Minitest::Test
84
84
  assert certs.length > 1
85
85
  end
86
86
 
87
+ def test_initialization_with_an_options_object
88
+ options_hash = {
89
+ namespace: 'test-namespace',
90
+ prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
91
+ }
92
+
93
+ options = Prefab::Options.new(options_hash)
94
+
95
+ client = Prefab::Client.new(options)
96
+
97
+ assert_equal client.namespace, 'test-namespace'
98
+ end
99
+
100
+ def test_initialization_with_a_hash
101
+ options_hash = {
102
+ namespace: 'test-namespace',
103
+ prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
104
+ }
105
+
106
+ client = Prefab::Client.new(options_hash)
107
+
108
+ assert_equal client.namespace, 'test-namespace'
109
+ end
110
+
87
111
  private
88
112
 
89
113
  def new_client(overrides = {})
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+ require 'timecop'
5
+
6
+ class TestLogPathCollector < Minitest::Test
7
+ def test_sync
8
+ Timecop.freeze do
9
+ client = new_client(namespace: 'this.is.a.namespace')
10
+
11
+ 2.times { client.log.info('here is a message') }
12
+ 3.times { client.log.error('here is a message') }
13
+
14
+ requests = []
15
+
16
+ client.define_singleton_method(:request) do |*params|
17
+ requests.push(params)
18
+ end
19
+
20
+ client.log_path_collector.send(:sync)
21
+
22
+ # let the flush thread run
23
+ sleep 0.01 while requests.length == 0
24
+
25
+ assert_equal requests, [[
26
+ Prefab::LoggerReportingService,
27
+ :send,
28
+ {
29
+ req_options: {},
30
+ params: Prefab::Loggers.new(
31
+ loggers: [Prefab::Logger.new(logger_name: 'test.test_log_path_collector.test_sync',
32
+ infos: 2, errors: 3)],
33
+ start_at: (Time.now.utc.to_f * 1000).to_i,
34
+ end_at: (Time.now.utc.to_f * 1000).to_i,
35
+ instance_hash: client.instance_hash,
36
+ namespace: 'this.is.a.namespace'
37
+ )
38
+ }
39
+ ]]
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def new_client(overrides = {})
46
+ options = Prefab::Options.new(**{
47
+ prefab_config_override_dir: 'none',
48
+ prefab_config_classpath_dir: 'test',
49
+ prefab_envs: ['unit_tests'],
50
+ api_key: '123-development-yourapikey-SDK',
51
+ collect_sync_interval: 1000 # we'll trigger sync manually in our test
52
+ }.merge(overrides))
53
+
54
+ Prefab::Client.new(options)
55
+ end
56
+ end
data/test/test_logger.rb CHANGED
@@ -47,37 +47,37 @@ class TestCLogger < Minitest::Test
47
47
  def test_level_of
48
48
  with_env('PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL', 'info') do
49
49
  # env var overrides the default level
50
- assert_equal Logger::INFO,
50
+ assert_equal ::Logger::INFO,
51
51
  @logger.level_of('app.models.user'), 'PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL is info'
52
52
 
53
53
  @logger.set_config_client(MockConfigClient.new({}))
54
- assert_equal Logger::WARN,
54
+ assert_equal ::Logger::WARN,
55
55
  @logger.level_of('app.models.user'), 'default is warn'
56
56
 
57
57
  @logger.set_config_client(MockConfigClient.new('log-level.app' => :INFO))
58
- assert_equal Logger::INFO,
58
+ assert_equal ::Logger::INFO,
59
59
  @logger.level_of('app.models.user')
60
60
 
61
61
  @logger.set_config_client(MockConfigClient.new('log-level.app' => :DEBUG))
62
- assert_equal Logger::DEBUG,
62
+ assert_equal ::Logger::DEBUG,
63
63
  @logger.level_of('app.models.user')
64
64
 
65
65
  @logger.set_config_client(MockConfigClient.new('log-level.app' => :DEBUG,
66
66
  'log-level.app.models' => :ERROR))
67
- assert_equal Logger::ERROR,
67
+ assert_equal ::Logger::ERROR,
68
68
  @logger.level_of('app.models.user'), 'test leveling'
69
69
  end
70
70
  end
71
71
 
72
72
  def test_log_internal
73
73
  logger, mock_logdev = mock_logger_expecting(/W, \[.*\] WARN -- cloud.prefab.client.test.path: : test message/)
74
- logger.log_internal('test message', 'test.path', '', Logger::WARN)
74
+ logger.log_internal('test message', 'test.path', '', ::Logger::WARN)
75
75
  mock_logdev.verify
76
76
  end
77
77
 
78
78
  def test_log_internal_unknown
79
79
  logger, mock_logdev = mock_logger_expecting(/A, \[.*\] ANY -- cloud.prefab.client.test.path: : test message/)
80
- logger.log_internal('test message', 'test.path', '', Logger::UNKNOWN)
80
+ logger.log_internal('test message', 'test.path', '', ::Logger::UNKNOWN)
81
81
  mock_logdev.verify
82
82
  end
83
83
 
@@ -85,30 +85,30 @@ class TestCLogger < Minitest::Test
85
85
  logger, mock_logdev = mock_logger_expecting(/W, \[.*\] WARN -- cloud.prefab.client.test.path: : should log/,
86
86
  calls: 2)
87
87
  logger.silence do
88
- logger.log_internal('should not log', 'test.path', '', Logger::WARN)
88
+ logger.log_internal('should not log', 'test.path', '', ::Logger::WARN)
89
89
  end
90
- logger.log_internal('should log', 'test.path', '', Logger::WARN)
90
+ logger.log_internal('should log', 'test.path', '', ::Logger::WARN)
91
91
  mock_logdev.verify
92
92
  end
93
93
 
94
94
  def test_log
95
95
  logger, mock_logdev = mock_logger_expecting(/W, \[.*\] WARN -- test.path: : test message/)
96
- logger.log('test message', 'test.path', '', Logger::WARN)
96
+ logger.log('test message', 'test.path', '', ::Logger::WARN)
97
97
  mock_logdev.verify
98
98
  end
99
99
 
100
100
  def test_log_unknown
101
101
  logger, mock_logdev = mock_logger_expecting(/A, \[.*\] ANY -- test.path: : test message/)
102
- logger.log('test message', 'test.path', '', Logger::UNKNOWN)
102
+ logger.log('test message', 'test.path', '', ::Logger::UNKNOWN)
103
103
  mock_logdev.verify
104
104
  end
105
105
 
106
106
  def test_log_silencing
107
107
  logger, mock_logdev = mock_logger_expecting(/W, \[.*\] WARN -- test.path: : should log/, calls: 2)
108
108
  logger.silence do
109
- logger.log('should not log', 'test.path', '', Logger::WARN)
109
+ logger.log('should not log', 'test.path', '', ::Logger::WARN)
110
110
  end
111
- logger.log('should log', 'test.path', '', Logger::WARN)
111
+ logger.log('should log', 'test.path', '', ::Logger::WARN)
112
112
  mock_logdev.verify
113
113
  end
114
114
 
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestOptions < Minitest::Test
6
+ API_KEY = 'abcdefg'
7
+
8
+ def test_works_with_named_arguments
9
+ assert_equal API_KEY, Prefab::Options.new(api_key: API_KEY).api_key
10
+ end
11
+
12
+ def test_works_with_hash
13
+ assert_equal API_KEY, Prefab::Options.new({ api_key: API_KEY }).api_key
14
+ end
15
+
16
+ def test_collect_max_paths
17
+ assert_equal 1000, Prefab::Options.new.collect_max_paths
18
+ assert_equal 100, Prefab::Options.new(collect_max_paths: 100).collect_max_paths
19
+ end
20
+
21
+ def test_collect_max_paths_with_local_only
22
+ options = Prefab::Options.new(collect_max_paths: 100,
23
+ prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY)
24
+ assert_equal 0, options.collect_max_paths
25
+ end
26
+
27
+ def test_collect_max_paths_with_collect_logs_false
28
+ options = Prefab::Options.new(collect_max_paths: 100,
29
+ collect_logs: false)
30
+ assert_equal 0, options.collect_max_paths
31
+ end
32
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prefab-cloud-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.21.0
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Dwyer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-27 00:00:00.000000000 Z
11
+ date: 2023-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -100,6 +100,20 @@ dependencies:
100
100
  - - ">="
101
101
  - !ruby/object:Gem::Version
102
102
  version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: uuid
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
103
117
  - !ruby/object:Gem::Dependency
104
118
  name: benchmark-ips
105
119
  requirement: !ruby/object:Gem::Requirement
@@ -221,6 +235,7 @@ files:
221
235
  - lib/prefab/feature_flag_client.rb
222
236
  - lib/prefab/internal_logger.rb
223
237
  - lib/prefab/local_config_parser.rb
238
+ - lib/prefab/log_path_collector.rb
224
239
  - lib/prefab/logger_client.rb
225
240
  - lib/prefab/murmer3.rb
226
241
  - lib/prefab/noop_cache.rb
@@ -247,7 +262,9 @@ files:
247
262
  - test/test_helper.rb
248
263
  - test/test_integration.rb
249
264
  - test/test_local_config_parser.rb
265
+ - test/test_log_path_collector.rb
250
266
  - test/test_logger.rb
267
+ - test/test_options.rb
251
268
  - test/test_weighted_value_resolver.rb
252
269
  homepage: http://github.com/prefab-cloud/prefab-cloud-ruby
253
270
  licenses: