prefab-cloud-ruby 0.21.0 → 0.22.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: 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: