unleash 6.4.1 → 6.5.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: 6c28fb398c92bd470282a94e7fe07b910fa4afed65381da67231c60c2b2960c2
4
- data.tar.gz: 3a5b78a63ccfcec23ed8eba1e2168a5be7f183ca0c3beac0eb6497d1d4d6678c
3
+ metadata.gz: 8f2aacf28333e06f4ca46369b9ece6fd1472a8e363cdc4a0fe1aeb0810d879a2
4
+ data.tar.gz: bc8e366ec18bff858f835260af79650ab6179daa6af93bf84a675eb3d74ad904
5
5
  SHA512:
6
- metadata.gz: 3d66480a2211bc60a0ddb2899d189a7966be1659f8618873233c8e419f2261dc5cf13cf79f747d36039af5f9ce1ead45fb4e3bc4140d420d4d59369a9a995bc4
7
- data.tar.gz: ba06afc53ff6d2806d6bbf18cc93e2287ae327f42cb6699f663a3f2f5bb9abc6e3f71ce02ba7fe18f48bdcb9fb1913c2d9cc4a6be2d05bae6f740def1a4281d5
6
+ metadata.gz: 9c397ef806b4e77fb8a1d8afbacd3ded420f5979fcf1999b03e524d77dca3f04205fa28e4a1e83037c7670837654c5f0e193f74b53654dedb034c1e9016fb22e
7
+ data.tar.gz: 07bfa665ecb17ffbe86b9dbb66c3247d824a27e27d846000f181ad5783d8af3a6e9adb9a47d2e2f37732bec80c137683609b63726cd58616164efe464a4fcd4c
data/CHANGELOG.md CHANGED
@@ -13,6 +13,11 @@ Note: These changes are not considered notable:
13
13
 
14
14
  ## [Unreleased]
15
15
 
16
+ ## [6.5.0] - 2026-01-29
17
+
18
+ ### Added
19
+ - Impact metrics
20
+
16
21
  ## [6.4.1] - 2025-12-04
17
22
 
18
23
  ### Changed
data/README.md CHANGED
@@ -28,7 +28,7 @@ You can use this client with [Unleash Enterprise](https://www.getunleash.io/pric
28
28
  Add this line to your application's Gemfile:
29
29
 
30
30
  ```ruby
31
- gem 'unleash', '~> 6.4.1'
31
+ gem 'unleash', '~> 6.5.0'
32
32
  ```
33
33
 
34
34
  And then execute:
@@ -534,6 +534,56 @@ Unleash.configure do |config|
534
534
  end
535
535
  ```
536
536
 
537
+ ## Impact metrics
538
+
539
+ Impact metrics are lightweight, application-level time-series metrics stored and visualized directly inside Unleash. They allow you to connect specific application data, such as request counts, error rates, or latency, to your feature flags and release plans.
540
+
541
+ These metrics help validate feature impact and automate release processes. For instance, you can monitor usage patterns or performance to determine if a feature meets its goals.
542
+
543
+ The SDK automatically attaches context labels to metrics: `appName` and `environment`.
544
+
545
+ ### Counters
546
+
547
+ Use counters for cumulative values that only increase (total requests, errors):
548
+
549
+ ```ruby
550
+ client = Unleash::Client.new
551
+
552
+ client.impact_metrics.define_counter(
553
+ 'request_count',
554
+ 'Total number of HTTP requests processed'
555
+ )
556
+
557
+ client.impact_metrics.increment_counter('request_count')
558
+ ```
559
+
560
+ ### Gauges
561
+
562
+ Use gauges for point-in-time values that can go up or down:
563
+
564
+ ```ruby
565
+ client.impact_metrics.define_gauge(
566
+ 'total_users',
567
+ 'Total number of registered users'
568
+ )
569
+
570
+ client.impact_metrics.update_gauge('total_users', user_count)
571
+ ```
572
+
573
+ ### Histograms
574
+
575
+ Histograms measure value distribution (request duration, response size):
576
+
577
+ ```ruby
578
+ client.impact_metrics.define_histogram(
579
+ 'request_time_ms',
580
+ 'Time taken to process a request in milliseconds',
581
+ [50, 100, 200, 500, 1000]
582
+ )
583
+
584
+ client.impact_metrics.observe_histogram('request_time_ms', 125)
585
+ ```
586
+
537
587
  ## Development
538
588
 
539
589
  After checking out the repo, run `bin/setup` to install dependencies.
@@ -6,12 +6,15 @@ require 'unleash/streaming_client_executor'
6
6
  require 'unleash/variant'
7
7
  require 'unleash/util/http'
8
8
  require 'unleash/util/event_source_wrapper'
9
+ require 'unleash/environment_resolver'
10
+ require 'unleash/impact_metrics'
9
11
  require 'logger'
10
12
  require 'time'
11
13
 
12
14
  module Unleash
13
15
  class Client
14
16
  attr_accessor :fetcher_scheduled_executor, :metrics_scheduled_executor
17
+ attr_reader :impact_metrics
15
18
 
16
19
  # rubocop:disable Metrics/AbcSize
17
20
  def initialize(*opts)
@@ -23,6 +26,8 @@ module Unleash
23
26
  Unleash.engine = YggdrasilEngine.new
24
27
  Unleash.engine.register_custom_strategies(Unleash.configuration.strategies.custom_strategies)
25
28
 
29
+ @impact_metrics = ImpactMetrics.new(Unleash.engine, Unleash.configuration.app_name)
30
+
26
31
  Unleash.toggle_fetcher = Unleash::ToggleFetcher.new Unleash.engine unless Unleash.configuration.streaming_mode?
27
32
 
28
33
  if Unleash.configuration.disable_client
@@ -107,6 +107,12 @@ module Unleash
107
107
  self.experimental_mode[:format] == 'delta'
108
108
  end
109
109
 
110
+ def generate_custom_http_headers
111
+ return self.custom_http_headers.call if self.custom_http_headers.respond_to?(:call)
112
+
113
+ self.custom_http_headers
114
+ end
115
+
110
116
  private
111
117
 
112
118
  def set_defaults
@@ -153,12 +159,6 @@ module Unleash
153
159
  raise ArgumentError, "custom_http_headers must be a Hash or a Proc."
154
160
  end
155
161
 
156
- def generate_custom_http_headers
157
- return self.custom_http_headers.call if self.custom_http_headers.respond_to?(:call)
158
-
159
- self.custom_http_headers
160
- end
161
-
162
162
  def set_option(opt, val)
163
163
  __send__("#{opt}=", val)
164
164
  rescue NoMethodError
@@ -0,0 +1,27 @@
1
+ module Unleash
2
+ class EnvironmentResolver
3
+ def self.extract_environment_from_custom_headers(custom_headers)
4
+ authorization_header = extract_authorization_header(custom_headers)
5
+ extract_environment_from_header(authorization_header)
6
+ end
7
+
8
+ def self.extract_authorization_header(custom_headers)
9
+ return nil if custom_headers.nil? || !custom_headers.is_a?(Hash)
10
+
11
+ key = custom_headers.keys.find{ |k| k.to_s.downcase == 'authorization' }
12
+ custom_headers[key] if key
13
+ end
14
+
15
+ def self.extract_environment_from_header(authorization_header)
16
+ return nil if authorization_header.nil? || authorization_header.empty?
17
+
18
+ after_colon = authorization_header.split(':', 2)[1]
19
+ return nil unless after_colon&.include?('.')
20
+
21
+ environment = after_colon.split('.')[0]
22
+ environment unless environment.empty?
23
+ end
24
+
25
+ private_class_method :extract_authorization_header, :extract_environment_from_header
26
+ end
27
+ end
@@ -0,0 +1,43 @@
1
+ module Unleash
2
+ class ImpactMetrics
3
+ def initialize(engine, app_name)
4
+ @engine = engine
5
+ @app_name = app_name
6
+ end
7
+
8
+ def define_counter(name, help_text)
9
+ @engine.define_counter(name, help_text)
10
+ end
11
+
12
+ def increment_counter(name, value = 1)
13
+ @engine.inc_counter(name, value, base_labels)
14
+ end
15
+
16
+ def define_gauge(name, help_text)
17
+ @engine.define_gauge(name, help_text)
18
+ end
19
+
20
+ def update_gauge(name, value)
21
+ @engine.set_gauge(name, value, base_labels)
22
+ end
23
+
24
+ def define_histogram(name, help_text, buckets = nil)
25
+ @engine.define_histogram(name, help_text, buckets)
26
+ end
27
+
28
+ def observe_histogram(name, value)
29
+ @engine.observe_histogram(name, value, base_labels)
30
+ end
31
+
32
+ private
33
+
34
+ def base_labels
35
+ {
36
+ 'appName' => @app_name,
37
+ 'environment' => EnvironmentResolver.extract_environment_from_custom_headers(
38
+ Unleash.configuration.generate_custom_http_headers
39
+ ) || Unleash.configuration.environment
40
+ }
41
+ end
42
+ end
43
+ end
@@ -23,7 +23,8 @@ module Unleash
23
23
  def post
24
24
  Unleash.logger.debug "post() Report"
25
25
 
26
- report = build_report
26
+ impact_metrics = collect_impact_metrics_safely
27
+ report = build_report(impact_metrics)
27
28
  return unless report
28
29
 
29
30
  send_report(report)
@@ -44,15 +45,20 @@ module Unleash
44
45
  }
45
46
  end
46
47
 
47
- def build_report
48
+ def build_report(impact_metrics)
48
49
  report = generate_report
49
- return nil if report.nil? && Time.now - self.last_time < LONGEST_WITHOUT_A_REPORT
50
+ has_data = !report.nil? || !impact_metrics.empty?
50
51
 
51
- report || generate_report_from_bucket({
52
+ return nil if !has_data && Time.now - self.last_time < LONGEST_WITHOUT_A_REPORT
53
+
54
+ report ||= generate_report_from_bucket({
52
55
  'start': self.last_time.utc.iso8601,
53
56
  'stop': Time.now.utc.iso8601,
54
57
  'toggles': {}
55
58
  })
59
+
60
+ report[:impactMetrics] = impact_metrics unless impact_metrics.empty?
61
+ report
56
62
  end
57
63
 
58
64
  def send_report(report)
@@ -66,8 +72,24 @@ module Unleash
66
72
  else
67
73
  # :nocov:
68
74
  Unleash.logger.error "Error when sending report to unleash server. Server responded with http code #{response.code}."
75
+ restore_impact_metrics(report[:impactMetrics])
69
76
  # :nocov:
70
77
  end
71
78
  end
79
+
80
+ def restore_impact_metrics(impact_metrics)
81
+ return if impact_metrics.nil? || impact_metrics.empty?
82
+
83
+ Unleash.engine&.restore_impact_metrics(impact_metrics)
84
+ rescue StandardError => e
85
+ Unleash.logger.warn "Failed to restore impact metrics: #{e.message}"
86
+ end
87
+
88
+ def collect_impact_metrics_safely
89
+ Unleash.engine&.collect_impact_metrics || []
90
+ rescue StandardError => e
91
+ Unleash.logger.warn "Failed to collect impact metrics: #{e.message}"
92
+ []
93
+ end
72
94
  end
73
95
  end
@@ -1,3 +1,3 @@
1
1
  module Unleash
2
- VERSION = "6.4.1".freeze
2
+ VERSION = "6.5.0".freeze
3
3
  end
@@ -24,12 +24,11 @@ Gem::Specification.new do |spec|
24
24
  spec.required_ruby_version = ">= 2.7"
25
25
 
26
26
  spec.add_dependency "ld-eventsource", "2.2.4" unless RUBY_ENGINE == 'jruby'
27
- spec.add_dependency "yggdrasil-engine", "~> 1.1.1"
27
+ spec.add_dependency "yggdrasil-engine", "~> 1.2.1"
28
28
 
29
29
  spec.add_dependency "base64", "~> 0.3.0"
30
30
  spec.add_dependency "logger", "~> 1.6"
31
31
 
32
- spec.add_development_dependency "bundler", "~> 2.1"
33
32
  spec.add_development_dependency "rake", "~> 12.3"
34
33
  spec.add_development_dependency "rspec", "~> 3.12"
35
34
  spec.add_development_dependency "rspec-json_expectations", "~> 2.2"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unleash
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.4.1
4
+ version: 6.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Renato Arruda
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-12-04 00:00:00.000000000 Z
11
+ date: 2026-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ld-eventsource
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.1.1
33
+ version: 1.2.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.1.1
40
+ version: 1.2.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: base64
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,20 +66,6 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '1.6'
69
- - !ruby/object:Gem::Dependency
70
- name: bundler
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '2.1'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '2.1'
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: rake
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -219,6 +205,8 @@ files:
219
205
  - lib/unleash/client.rb
220
206
  - lib/unleash/configuration.rb
221
207
  - lib/unleash/context.rb
208
+ - lib/unleash/environment_resolver.rb
209
+ - lib/unleash/impact_metrics.rb
222
210
  - lib/unleash/metrics_reporter.rb
223
211
  - lib/unleash/scheduled_executor.rb
224
212
  - lib/unleash/spec_version.rb
@@ -251,7 +239,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
251
239
  - !ruby/object:Gem::Version
252
240
  version: '0'
253
241
  requirements: []
254
- rubygems_version: 3.4.20
242
+ rubygems_version: 3.5.22
255
243
  signing_key:
256
244
  specification_version: 4
257
245
  summary: Unleash feature toggle client.