launchdarkly-server-sdk 7.3.3 → 8.0.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: 46965b7a093300bb83c4a872fa78b1b655b516d12bb8156a146485cdb64aa3e9
4
- data.tar.gz: '08e28a273df0f5b682869c8109305f517ca7be7f519e87f58c86f27d2216b950'
3
+ metadata.gz: bdb1eeb75ade6b2f82e1271c763f74ae577da09510e585b7cc1076ae9ff594d7
4
+ data.tar.gz: 8f794b216dff21125547319781efc2d77ce1632af6faccd8fa3c7fd96e2c8216
5
5
  SHA512:
6
- metadata.gz: c0db4820e8eb87d27719e5789bcc28240e905f204b53697abdf795a61b373af27d2676090b8deacb5b3a1efc71389f294933922d53317b82470de27d70f70401
7
- data.tar.gz: 5bc0ccc0da47626efd8c5674daeac8b928d3a2c6bbf4fef73212ace4b5138028d9cce458d6c945c118092386123f58bf32c3a9de67339c86a4581596db9f2164
6
+ metadata.gz: 1ad1349bf430c6bb3a821d033f11106883c25d68aadc10bd622809b4c6dfe04e55ada3b9a49b8c92588389a4b3fb2112b1fd4fb63cdedf264cd3a0cc0bc0ac26
7
+ data.tar.gz: 6a605bd4f7ea9c7af3454e9a70bf5b3db1057456e5fc96249c807a706b0ca2fa8f0d3bd1238dae7e2fa5778ffe5d3b8728c9085499df0d952f76b3e41dcfa7a7
data/README.md CHANGED
@@ -3,7 +3,7 @@ LaunchDarkly Server-side SDK for Ruby
3
3
 
4
4
  [![Gem Version](https://badge.fury.io/rb/launchdarkly-server-sdk.svg)](http://badge.fury.io/rb/launchdarkly-server-sdk)
5
5
 
6
- [![Run CI](https://github.com/launchdarkly/ruby-server-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/launchdarkly/ruby-server-sdk/actions/workflows/ci.yml)
6
+ [![Circle CI](https://circleci.com/gh/launchdarkly/ruby-server-sdk/tree/main.svg?style=svg)](https://circleci.com/gh/launchdarkly/ruby-server-sdk/tree/main)
7
7
  [![RubyDoc](https://img.shields.io/static/v1?label=docs+-+all+versions&message=reference&color=00add8)](https://www.rubydoc.info/gems/launchdarkly-server-sdk)
8
8
  [![GitHub Pages](https://img.shields.io/static/v1?label=docs+-+latest&message=reference&color=00add8)](https://launchdarkly.github.io/ruby-server-sdk)
9
9
 
@@ -13,18 +13,6 @@ module LaunchDarkly
13
13
  #
14
14
  # Constructor for creating custom LaunchDarkly configurations.
15
15
  #
16
- # `user_keys_capacity` and `user_keys_flush_interval` are deprecated
17
- # configuration options. They exist to maintain backwards compatibility
18
- # with previous configurations. Newer code should prefer their replacement
19
- # options -- `context_keys_capacity` and `context_keys_flush_interval`.
20
- #
21
- # In the event both the user and context variations are provided, the
22
- # context specific configuration option will take precedence.
23
- #
24
- # Similarly, `private_attribute_names` is deprecated. Newer code should
25
- # prefer `private_attributes`. If both are provided, `private_attributes`
26
- # will take precedence.
27
- #
28
16
  # @param opts [Hash] the configuration options
29
17
  # @option opts [Logger] :logger See {#logger}.
30
18
  # @option opts [String] :base_uri ("https://sdk.launchdarkly.com") See {#base_uri}.
@@ -42,12 +30,9 @@ module LaunchDarkly
42
30
  # @option opts [Float] :poll_interval (30) See {#poll_interval}.
43
31
  # @option opts [Boolean] :stream (true) See {#stream?}.
44
32
  # @option opts [Boolean] all_attributes_private (false) See {#all_attributes_private}.
45
- # @option opts [Array] :private_attribute_names See {#private_attribute_names}.
46
33
  # @option opts [Array] :private_attributes See {#private_attributes}.
47
34
  # @option opts [Boolean] :send_events (true) See {#send_events}.
48
- # @option opts [Integer] :user_keys_capacity (1000) See {#user_keys_capacity}.
49
35
  # @option opts [Integer] :context_keys_capacity (1000) See {#context_keys_capacity}.
50
- # @option opts [Float] :user_keys_flush_interval (300) See {#user_keys_flush_interval}.
51
36
  # @option opts [Float] :context_keys_flush_interval (300) See {#context_keys_flush_interval}.
52
37
  # @option opts [Object] :data_source See {#data_source}.
53
38
  # @option opts [Boolean] :diagnostic_opt_out (false) See {#diagnostic_opt_out?}.
@@ -76,10 +61,10 @@ module LaunchDarkly
76
61
  @offline = opts.has_key?(:offline) ? opts[:offline] : Config.default_offline
77
62
  @poll_interval = opts.has_key?(:poll_interval) && opts[:poll_interval] > Config.default_poll_interval ? opts[:poll_interval] : Config.default_poll_interval
78
63
  @all_attributes_private = opts[:all_attributes_private] || false
79
- @private_attributes = opts[:private_attributes] || opts[:private_attribute_names] || []
64
+ @private_attributes = opts[:private_attributes] || []
80
65
  @send_events = opts.has_key?(:send_events) ? opts[:send_events] : Config.default_send_events
81
- @context_keys_capacity = opts[:context_keys_capacity] || opts[:user_keys_capacity] || Config.default_context_keys_capacity
82
- @context_keys_flush_interval = opts[:context_keys_flush_interval] || opts[:user_keys_flush_interval] || Config.default_user_keys_flush_interval
66
+ @context_keys_capacity = opts[:context_keys_capacity] || Config.default_context_keys_capacity
67
+ @context_keys_flush_interval = opts[:context_keys_flush_interval] || Config.default_context_keys_flush_interval
83
68
  @data_source = opts[:data_source]
84
69
  @diagnostic_opt_out = opts.has_key?(:diagnostic_opt_out) && opts[:diagnostic_opt_out]
85
70
  @diagnostic_recording_interval = opts.has_key?(:diagnostic_recording_interval) && opts[:diagnostic_recording_interval] > Config.minimum_diagnostic_recording_interval ?
@@ -258,14 +243,6 @@ module LaunchDarkly
258
243
  #
259
244
  attr_reader :private_attributes
260
245
 
261
- #
262
- # @deprecated Backwards compatibility alias for #private_attributes.
263
- #
264
- # @return [Integer]
265
- # @see #private_attributes
266
- #
267
- alias :private_attribute_names :private_attributes
268
-
269
246
  #
270
247
  # Whether to send events back to LaunchDarkly. This differs from {#offline?} in that it affects
271
248
  # only the sending of client-side events, not streaming or polling for events from the server.
@@ -281,14 +258,6 @@ module LaunchDarkly
281
258
  #
282
259
  attr_reader :context_keys_capacity
283
260
 
284
- #
285
- # @deprecated Backwards compatibility alias for #context_keys_capacity.
286
- #
287
- # @return [Integer]
288
- # @see #context_keys_flush_interval
289
- #
290
- alias :user_keys_capacity :context_keys_capacity
291
-
292
261
  #
293
262
  # The interval in seconds at which the event processor will reset its set of known context keys.
294
263
  # @return [Float]
@@ -296,14 +265,6 @@ module LaunchDarkly
296
265
  #
297
266
  attr_reader :context_keys_flush_interval
298
267
 
299
- #
300
- # @deprecated Backwards compatibility alias for #context_keys_flush_interval.
301
- #
302
- # @return [Integer]
303
- # @see #context_keys_flush_interval
304
- #
305
- alias :user_keys_flush_interval :context_keys_flush_interval
306
-
307
268
  #
308
269
  # An object that is responsible for receiving feature flag data from LaunchDarkly. By default,
309
270
  # the client uses its standard polling or streaming implementation; this is customizable for
@@ -570,18 +531,6 @@ module LaunchDarkly
570
531
  300
571
532
  end
572
533
 
573
- class << self
574
- #
575
- # @deprecated Backwards compatibility alias for #default_context_keys_capacity
576
- #
577
- alias :default_user_keys_capacity :default_context_keys_capacity
578
-
579
- #
580
- # @deprecated Backwards compatibility alias for #default_context_keys_flush_interval
581
- #
582
- alias :default_user_keys_flush_interval :default_context_keys_flush_interval
583
- end
584
-
585
534
  #
586
535
  # The default value for {#diagnostic_recording_interval}.
587
536
  # @return [Float] 900
@@ -647,25 +596,11 @@ module LaunchDarkly
647
596
  # @return [Integer]
648
597
  attr_reader :context_cache_size
649
598
 
650
- #
651
- # @deprecated Backwards compatibility alias for #context_cache_size
652
- #
653
- # @return [Integer]
654
- #
655
- alias :user_cache_size :context_cache_size
656
-
657
599
  # The maximum length of time (in seconds) that the Big Segment state for a context will be cached
658
600
  # by the SDK.
659
601
  # @return [Float]
660
602
  attr_reader :context_cache_time
661
603
 
662
- #
663
- # @deprecated Backwards compatibility alias for #context_cache_time
664
- #
665
- # @return [Float]
666
- #
667
- alias :user_cache_time :context_cache_time
668
-
669
604
  # The interval (in seconds) at which the SDK will poll the Big Segment store to make sure it is
670
605
  # available and to determine how long ago it was updated.
671
606
  # @return [Float]
@@ -324,7 +324,6 @@ module LaunchDarkly
324
324
  #
325
325
  def self.create(data)
326
326
  return create_invalid_context(ERR_NOT_HASH) unless data.is_a?(Hash)
327
- return create_legacy_context(data) unless data.has_key?(:kind)
328
327
 
329
328
  kind = data[:kind]
330
329
  if kind == KIND_MULTI
@@ -394,52 +393,6 @@ module LaunchDarkly
394
393
  new(nil, nil, nil, nil, false, nil, nil, error)
395
394
  end
396
395
 
397
- #
398
- # @param data [Hash]
399
- # @return [LDContext]
400
- #
401
- private_class_method def self.create_legacy_context(data)
402
- warn("DEPRECATED: legacy user format will be removed in 8.0.0", uplevel: 1)
403
-
404
- key = data[:key]
405
-
406
- # Legacy users are allowed to have "" as a key but they cannot have nil as a key.
407
- return create_invalid_context(ERR_KEY_EMPTY) if key.nil?
408
-
409
- name = data[:name]
410
- name_error = LaunchDarkly::Impl::Context.validate_name(name)
411
- return create_invalid_context(name_error) unless name_error.nil?
412
-
413
- anonymous = data[:anonymous]
414
- anonymous_error = LaunchDarkly::Impl::Context.validate_anonymous(anonymous, true)
415
- return create_invalid_context(anonymous_error) unless anonymous_error.nil?
416
-
417
- custom = data[:custom]
418
- unless custom.nil? || custom.is_a?(Hash)
419
- return create_invalid_context(ERR_CUSTOM_NON_HASH)
420
- end
421
-
422
- # We only need to create an attribute hash if one of these keys exist.
423
- # Everything else is stored in dedicated instance variables.
424
- attributes = custom.clone
425
- data.each do |k, v|
426
- case k
427
- when :ip, :email, :avatar, :firstName, :lastName, :country
428
- attributes ||= {}
429
- attributes[k] = v.clone
430
- else
431
- next
432
- end
433
- end
434
-
435
- private_attributes = data[:privateAttributeNames]
436
- if private_attributes && !private_attributes.is_a?(Array)
437
- return create_invalid_context(ERR_PRIVATE_NON_ARRAY)
438
- end
439
-
440
- new(key.to_s, key.to_s, KIND_DEFAULT, name, anonymous, attributes, private_attributes)
441
- end
442
-
443
396
  #
444
397
  # @param data [Hash]
445
398
  # @param kind [String]
@@ -1,4 +1,3 @@
1
-
2
1
  module LaunchDarkly
3
2
  # An object returned by {LDClient#variation_detail}, combining the result of a flag evaluation with
4
3
  # an explanation of how it was calculated.
@@ -13,6 +12,7 @@ module LaunchDarkly
13
12
  def initialize(value, variation_index, reason)
14
13
  raise ArgumentError.new("variation_index must be a number") if !variation_index.nil? && !(variation_index.is_a? Numeric)
15
14
  raise ArgumentError.new("reason must be an EvaluationReason") unless reason.is_a? EvaluationReason
15
+
16
16
  @value = value
17
17
  @variation_index = variation_index
18
18
  @reason = reason
@@ -100,6 +100,10 @@ module LaunchDarkly
100
100
  # a rule specified a nonexistent variation. An error message will always be logged in this case.
101
101
  ERROR_MALFORMED_FLAG = :MALFORMED_FLAG
102
102
 
103
+ # Value for {#error_kind} indicating that there was an inconsistency between the expected type of the flag, and the
104
+ # actual type of the variation evaluated.
105
+ ERROR_WRONG_TYPE = :WRONG_TYPE
106
+
103
107
  # Value for {#error_kind} indicating that the caller passed `nil` for the context parameter, or the
104
108
  # context was invalid.
105
109
  ERROR_USER_NOT_SPECIFIED = :USER_NOT_SPECIFIED
@@ -40,7 +40,9 @@ module LaunchDarkly
40
40
  default = nil,
41
41
  track_events = false,
42
42
  debug_until = nil,
43
- prereq_of = nil
43
+ prereq_of = nil,
44
+ sampling_ratio = nil,
45
+ exclude_from_summaries = false
44
46
  )
45
47
  end
46
48
 
@@ -55,6 +57,9 @@ module LaunchDarkly
55
57
  )
56
58
  end
57
59
 
60
+ def record_migration_op_event(event)
61
+ end
62
+
58
63
  def flush
59
64
  end
60
65
 
@@ -153,10 +158,12 @@ module LaunchDarkly
153
158
  default = nil,
154
159
  track_events = false,
155
160
  debug_until = nil,
156
- prereq_of = nil
161
+ prereq_of = nil,
162
+ sampling_ratio = nil,
163
+ exclude_from_summaries = false
157
164
  )
158
165
  post_to_inbox(LaunchDarkly::Impl::EvalEvent.new(timestamp, context, key, version, variation, value, reason,
159
- default, track_events, debug_until, prereq_of))
166
+ default, track_events, debug_until, prereq_of, sampling_ratio, exclude_from_summaries))
160
167
  end
161
168
 
162
169
  def record_identify_event(context)
@@ -167,6 +174,10 @@ module LaunchDarkly
167
174
  post_to_inbox(LaunchDarkly::Impl::CustomEvent.new(timestamp, context, key, data, metric_value))
168
175
  end
169
176
 
177
+ def record_migration_op_event(event)
178
+ post_to_inbox(event)
179
+ end
180
+
170
181
  def flush
171
182
  # flush is done asynchronously
172
183
  post_to_inbox(FlushMessage.new)
@@ -220,6 +231,7 @@ module LaunchDarkly
220
231
  @config = config
221
232
  @diagnostic_accumulator = config.diagnostic_opt_out? ? nil : diagnostic_accumulator
222
233
  @event_sender = event_sender
234
+ @sampler = LaunchDarkly::Impl::Sampler.new(Random.new)
223
235
 
224
236
  @context_keys = SimpleLRUCacheSet.new(config.context_keys_capacity)
225
237
  @formatter = EventOutputFormatter.new(config)
@@ -292,7 +304,7 @@ module LaunchDarkly
292
304
  return if @disabled.value
293
305
 
294
306
  # Always record the event in the summary.
295
- outbox.add_to_summary(event)
307
+ outbox.add_to_summary(event) unless event.exclude_from_summaries
296
308
 
297
309
  # Decide whether to add the event to the payload. Feature events may be added twice, once for
298
310
  # the event (if tracked) and once for debugging.
@@ -309,12 +321,12 @@ module LaunchDarkly
309
321
 
310
322
  # For each context we haven't seen before, we add an index event - unless this is already
311
323
  # an identify event for that context.
312
- if !event.context.nil? && !notice_context(event.context) && !event.is_a?(LaunchDarkly::Impl::IdentifyEvent)
324
+ if !event.context.nil? && !notice_context(event.context) && !event.is_a?(LaunchDarkly::Impl::IdentifyEvent) && !event.is_a?(LaunchDarkly::Impl::MigrationOpEvent)
313
325
  outbox.add_event(LaunchDarkly::Impl::IndexEvent.new(event.timestamp, event.context))
314
326
  end
315
327
 
316
- outbox.add_event(event) if will_add_full_event
317
- outbox.add_event(debug_event) unless debug_event.nil?
328
+ outbox.add_event(event) if will_add_full_event && @sampler.sample(event.sampling_ratio.nil? ? 1 : event.sampling_ratio)
329
+ outbox.add_event(debug_event) if !debug_event.nil? && @sampler.sample(event.sampling_ratio.nil? ? 1 : event.sampling_ratio)
318
330
  end
319
331
 
320
332
  #
@@ -443,6 +455,7 @@ module LaunchDarkly
443
455
  CUSTOM_KIND = 'custom'
444
456
  INDEX_KIND = 'index'
445
457
  DEBUG_KIND = 'debug'
458
+ MIGRATION_OP_KIND = 'migration_op'
446
459
  SUMMARY_KIND = 'summary'
447
460
 
448
461
  def initialize(config)
@@ -476,6 +489,64 @@ module LaunchDarkly
476
489
  out[:reason] = event.reason unless event.reason.nil?
477
490
  out
478
491
 
492
+ when LaunchDarkly::Impl::MigrationOpEvent
493
+ out = {
494
+ kind: MIGRATION_OP_KIND,
495
+ creationDate: event.timestamp,
496
+ contextKeys: event.context.keys,
497
+ operation: event.operation.to_s,
498
+ evaluation: {
499
+ key: event.key,
500
+ value: event.evaluation.value,
501
+ },
502
+ }
503
+
504
+ out[:evaluation][:version] = event.version unless event.version.nil?
505
+ out[:evaluation][:default] = event.default unless event.default.nil?
506
+ out[:evaluation][:variation] = event.evaluation.variation_index unless event.evaluation.variation_index.nil?
507
+ out[:evaluation][:reason] = event.evaluation.reason unless event.evaluation.reason.nil?
508
+ out[:samplingRatio] = event.sampling_ratio unless event.sampling_ratio.nil? || event.sampling_ratio == 1
509
+
510
+ measurements = []
511
+
512
+ unless event.invoked.empty?
513
+ measurements << {
514
+ "key": "invoked",
515
+ "values": event.invoked.map { |origin| [origin, true] }.to_h,
516
+ }
517
+ end
518
+
519
+ unless event.consistency_check.nil?
520
+ measurement = {
521
+ "key": "consistent",
522
+ "value": event.consistency_check,
523
+ }
524
+
525
+ unless event.consistency_check_ratio.nil? || event.consistency_check_ratio == 1
526
+ measurement[:samplingRatio] = event.consistency_check_ratio
527
+ end
528
+
529
+ measurements << measurement
530
+ end
531
+
532
+
533
+ unless event.latencies.empty?
534
+ measurements << {
535
+ "key": "latency_ms",
536
+ "values": event.latencies,
537
+ }
538
+ end
539
+
540
+ unless event.errors.empty?
541
+ measurements << {
542
+ "key": "error",
543
+ "values": event.errors.map { |origin| [origin, true] }.to_h,
544
+ }
545
+ end
546
+ out[:measurements] = measurements unless measurements.empty?
547
+
548
+ out
549
+
479
550
  when LaunchDarkly::Impl::IdentifyEvent
480
551
  {
481
552
  kind: IDENTIFY_KIND,
@@ -23,7 +23,7 @@ module LaunchDarkly
23
23
  @last_status = nil
24
24
 
25
25
  unless @store.nil?
26
- @cache = ExpiringCache.new(big_segments_config.user_cache_size, big_segments_config.user_cache_time)
26
+ @cache = ExpiringCache.new(big_segments_config.context_cache_size, big_segments_config.context_cache_time)
27
27
  @poll_worker = RepeatingTask.new(big_segments_config.status_poll_interval, 0, -> { poll_store_and_update_status }, logger)
28
28
  @poll_worker.start
29
29
  end
@@ -1,23 +1,33 @@
1
+ require 'set'
2
+
1
3
  module LaunchDarkly
2
4
  module Impl
3
5
  class Event
4
6
  # @param timestamp [Integer]
5
7
  # @param context [LaunchDarkly::LDContext]
6
- def initialize(timestamp, context)
8
+ # @param sampling_ratio [Integer, nil]
9
+ # @param exclude_from_summaries [Boolean]
10
+ def initialize(timestamp, context, sampling_ratio = nil, exclude_from_summaries = false)
7
11
  @timestamp = timestamp
8
12
  @context = context
13
+ @sampling_ratio = sampling_ratio
14
+ @exclude_from_summaries = exclude_from_summaries
9
15
  end
10
16
 
11
17
  # @return [Integer]
12
18
  attr_reader :timestamp
13
19
  # @return [LaunchDarkly::LDContext]
14
20
  attr_reader :context
21
+ # @return [Integer, nil]
22
+ attr_reader :sampling_ratio
23
+ # @return [Boolean]
24
+ attr_reader :exclude_from_summaries
15
25
  end
16
26
 
17
27
  class EvalEvent < Event
18
28
  def initialize(timestamp, context, key, version = nil, variation = nil, value = nil, reason = nil, default = nil,
19
- track_events = false, debug_until = nil, prereq_of = nil)
20
- super(timestamp, context)
29
+ track_events = false, debug_until = nil, prereq_of = nil, sampling_ratio = nil, exclude_from_summaries = false)
30
+ super(timestamp, context, sampling_ratio, exclude_from_summaries)
21
31
  @key = key
22
32
  @version = version
23
33
  @variation = variation
@@ -41,6 +51,54 @@ module LaunchDarkly
41
51
  attr_reader :prereq_of
42
52
  end
43
53
 
54
+ class MigrationOpEvent < Event
55
+ #
56
+ # A migration op event represents the results of a migration-assisted read or write operation.
57
+ #
58
+ # The event includes optional measurements reporting on consistency checks, error reporting, and operation latency
59
+ # values.
60
+ #
61
+ # @param timestamp [Integer]
62
+ # @param context [LaunchDarkly::LDContext]
63
+ # @param key [string]
64
+ # @param flag [LaunchDarkly::Impl::Model::FeatureFlag, nil]
65
+ # @param operation [Symbol]
66
+ # @param default_stage [Symbol]
67
+ # @param evaluation [LaunchDarkly::EvaluationDetail]
68
+ # @param invoked [Set]
69
+ # @param consistency_check [Boolean, nil]
70
+ # @param consistency_check_ratio [Integer, nil]
71
+ # @param errors [Set]
72
+ # @param latencies [Hash<Symbol, Float>]
73
+ #
74
+ def initialize(timestamp, context, key, flag, operation, default_stage, evaluation, invoked, consistency_check, consistency_check_ratio, errors, latencies)
75
+ super(timestamp, context)
76
+ @operation = operation
77
+ @key = key
78
+ @version = flag&.version
79
+ @sampling_ratio = flag&.sampling_ratio
80
+ @default = default_stage
81
+ @evaluation = evaluation
82
+ @consistency_check = consistency_check
83
+ @consistency_check_ratio = consistency_check.nil? ? nil : consistency_check_ratio
84
+ @invoked = invoked
85
+ @errors = errors
86
+ @latencies = latencies
87
+ end
88
+
89
+ attr_reader :operation
90
+ attr_reader :key
91
+ attr_reader :version
92
+ attr_reader :sampling_ratio
93
+ attr_reader :default
94
+ attr_reader :evaluation
95
+ attr_reader :consistency_check
96
+ attr_reader :consistency_check_ratio
97
+ attr_reader :invoked
98
+ attr_reader :errors
99
+ attr_reader :latencies
100
+ end
101
+
44
102
  class IdentifyEvent < Event
45
103
  def initialize(timestamp, context)
46
104
  super(timestamp, context)
@@ -112,7 +112,7 @@ module LaunchDarkly
112
112
  @pool = create_redis_pool(opts)
113
113
 
114
114
  # shutdown pool on close unless the client passed a custom pool and specified not to shutdown
115
- @pool_shutdown_on_close = !opts[:pool] || opts.fetch(:pool_shutdown_on_close, true)
115
+ @pool_shutdown_on_close = (!opts[:pool] || opts.fetch(:pool_shutdown_on_close, true))
116
116
 
117
117
  @prefix = opts[:prefix] || LaunchDarkly::Integrations::Redis::default_prefix
118
118
  @logger = opts[:logger] || Config.default_logger