eppo-server-sdk 3.2.8 → 3.4.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.
data/README.md CHANGED
@@ -7,6 +7,23 @@ Refer to our [SDK documentation](https://docs.geteppo.com/feature-flags/sdks/rub
7
7
  ## Supported Ruby Versions
8
8
  This version of the SDK is compatible with Ruby 3.0.6 and above.
9
9
 
10
+ ## Logging
11
+
12
+ Ruby SDK uses [`env_logger`](https://docs.rs/env_logger/) for logging.
13
+
14
+ Starting from version 3.3.0, the log level can be configured via `EPPO_LOG` environment variable using one of the following values:
15
+ - `off`
16
+ - `error`
17
+ - `warn`
18
+ - `info` (default)
19
+ - `debug`
20
+ - `trace`
21
+
22
+ Alternatively, it can be configured using `log_level` parameter for `EppoClient::Config` constructor:
23
+ ```ruby
24
+ config = EppoClient::Config.new("sdk-key", log_level: "debug")
25
+ ```
26
+
10
27
  # Contributing
11
28
 
12
29
  ## Testing with local version of `eppo_core`
@@ -21,8 +38,40 @@ eppo_core = { path = '../eppo_core' }
21
38
 
22
39
  Make sure you remove the override before updating `Cargo.lock`. Otherwise, the lock file will be missing `eppo_core` checksum and will be unsuitable for release. (CI will warn you if you do this accidentally.)
23
40
 
41
+ ## Build locally
42
+
43
+ Install all dependencies:
44
+ ```sh
45
+ bundle install
46
+ ```
47
+
48
+ Build native extension:
49
+ ```sh
50
+ bundle exec rake build
51
+ ```
52
+
53
+ Run tests:
54
+ ```sh
55
+ bundle exec rspec
56
+ ```
57
+
24
58
  ## Releasing
25
59
 
26
60
  * Bump versions in `ruby-sdk/lib/eppo_client/version.rb` and `ruby-sdk/ext/eppo_client/Cargo.toml`
27
61
  * Run `cargo update --workspace --verbose` to update `Cargo.lock`
28
62
  * Run `bundle` to update `Gemfile.lock`
63
+
64
+
65
+ ## Building Ruby native lib
66
+
67
+ 1. Clone this repository at the desired Ruby SDK tag, eg.: `git clone --depth 1 --branch ruby-sdk@x.y.z https://github.com/Eppo-exp/eppo-multiplatform.git`
68
+ 2. Open `build.sh` and update `rb-sys-dock --platform <platform>` with the desired platform, eg.: `rb-sys-dock --platform x86_64-linux`
69
+ 3. Run `docker build --build-arg WORKDIR=$(pwd) -f Dockerfile.ruby.build -t ruby-sdk-builder .` to build the builder docker image
70
+ 4. Run the following command to build the gem with native lib:
71
+ ```
72
+ mkdir -p rust/cargo/registry && docker run --rm -it \
73
+ -v /var/run/docker.sock:/var/run/docker.sock \
74
+ -v /tmp:/tmp -v \
75
+ $(pwd)/rust:$(pwd)/rust ruby-sdk-builder
76
+ ```
77
+ 5. The gem will be available at `ruby-sdk/pkg/eppo-server-sdk-x.y.z-arch.gem`
@@ -1,18 +1,18 @@
1
1
  [package]
2
2
  name = "eppo_client"
3
3
  # TODO: this version and lib/eppo_client/version.rb should be in sync
4
- version = "3.2.8"
4
+ version = "3.4.0"
5
5
  edition = "2021"
6
6
  license = "MIT"
7
7
  publish = false
8
- rust-version = "1.71.1"
8
+ rust-version = "1.75.0"
9
9
 
10
10
  [lib]
11
11
  crate-type = ["cdylib"]
12
12
 
13
13
  [dependencies]
14
14
  env_logger = { version = "0.11.3", features = ["unstable-kv"] }
15
- eppo_core = { version = "4.1.0", features = ["vendored"] }
15
+ eppo_core = { version = "=7.0.0", features = ["vendored", "magnus"] }
16
16
  log = { version = "0.4.21", features = ["kv_serde"] }
17
17
  magnus = { version = "0.6.4" }
18
18
  serde = { version = "1.0.203", features = ["derive"] }
@@ -1,4 +1,4 @@
1
- use std::{cell::RefCell, sync::Arc, time::Duration};
1
+ use std::{cell::RefCell, str::FromStr, sync::Arc, time::Duration};
2
2
 
3
3
  use eppo_core::{
4
4
  configuration_fetcher::{ConfigurationFetcher, ConfigurationFetcherConfig},
@@ -8,7 +8,7 @@ use eppo_core::{
8
8
  ufc::VariationType,
9
9
  Attributes, ContextAttributes,
10
10
  };
11
- use magnus::{error::Result, exception, prelude::*, Error, TryConvert, Value};
11
+ use magnus::{error::Result, exception, prelude::*, Error, IntoValue, Ruby, TryConvert, Value};
12
12
 
13
13
  use crate::{configuration::Configuration, SDK_METADATA};
14
14
 
@@ -19,6 +19,7 @@ pub struct Config {
19
19
  base_url: String,
20
20
  poll_interval: Option<Duration>,
21
21
  poll_jitter: Duration,
22
+ log_level: Option<log::LevelFilter>,
22
23
  }
23
24
 
24
25
  impl TryConvert for Config {
@@ -29,11 +30,22 @@ impl TryConvert for Config {
29
30
  let poll_interval_seconds =
30
31
  Option::<u64>::try_convert(val.funcall("poll_interval_seconds", ())?)?;
31
32
  let poll_jitter_seconds = u64::try_convert(val.funcall("poll_jitter_seconds", ())?)?;
33
+
34
+ let log_level = {
35
+ let s = Option::<String>::try_convert(val.funcall("log_level", ())?)?;
36
+ s.map(|s| {
37
+ log::LevelFilter::from_str(&s)
38
+ .map_err(|err| Error::new(exception::runtime_error(), err.to_string()))
39
+ })
40
+ .transpose()?
41
+ };
42
+
32
43
  Ok(Config {
33
44
  api_key,
34
45
  base_url,
35
46
  poll_interval: poll_interval_seconds.map(Duration::from_secs),
36
47
  poll_jitter: Duration::from_secs(poll_jitter_seconds),
48
+ log_level,
37
49
  })
38
50
  }
39
51
  }
@@ -52,6 +64,23 @@ pub struct Client {
52
64
 
53
65
  impl Client {
54
66
  pub fn new(config: Config) -> Client {
67
+ // Initialize logger
68
+ {
69
+ let mut builder = env_logger::Builder::from_env(
70
+ env_logger::Env::new()
71
+ .filter_or("EPPO_LOG", "eppo=info")
72
+ .write_style("EPPO_LOG_STYLE"),
73
+ );
74
+
75
+ if let Some(log_level) = config.log_level {
76
+ builder.filter_module("eppo", log_level);
77
+ }
78
+
79
+ // Logger can only be set once, so we ignore the initialization error here if client is
80
+ // re-initialized.
81
+ let _ = builder.try_init();
82
+ };
83
+
55
84
  let configuration_store = Arc::new(ConfigurationStore::new());
56
85
 
57
86
  let poller_thread = if let Some(poll_interval) = config.poll_interval {
@@ -87,7 +116,8 @@ impl Client {
87
116
  }
88
117
 
89
118
  pub fn get_assignment(
90
- &self,
119
+ ruby: &Ruby,
120
+ rb_self: &Self,
91
121
  flag_key: String,
92
122
  subject_key: String,
93
123
  subject_attributes: Value,
@@ -96,7 +126,7 @@ impl Client {
96
126
  let expected_type: VariationType = serde_magnus::deserialize(expected_type)?;
97
127
  let subject_attributes: Attributes = serde_magnus::deserialize(subject_attributes)?;
98
128
 
99
- let result = self
129
+ let result = rb_self
100
130
  .evaluator
101
131
  .get_assignment(
102
132
  &flag_key,
@@ -107,7 +137,7 @@ impl Client {
107
137
  // TODO: maybe expose possible errors individually.
108
138
  .map_err(|err| Error::new(exception::runtime_error(), err.to_string()))?;
109
139
 
110
- Ok(serde_magnus::serialize(&result).expect("assignment value should be serializable"))
140
+ Ok(result.into_value_with(&ruby))
111
141
  }
112
142
 
113
143
  pub fn get_assignment_details(
@@ -117,6 +147,8 @@ impl Client {
117
147
  subject_attributes: Value,
118
148
  expected_type: Value,
119
149
  ) -> Result<Value> {
150
+ let ruby = Ruby::get_with(subject_attributes);
151
+
120
152
  let expected_type: VariationType = serde_magnus::deserialize(expected_type)?;
121
153
  let subject_attributes: Attributes = serde_magnus::deserialize(subject_attributes)?;
122
154
 
@@ -127,7 +159,7 @@ impl Client {
127
159
  Some(expected_type),
128
160
  );
129
161
 
130
- Ok(serde_magnus::serialize(&result).expect("assignment value should be serializable"))
162
+ Ok(result.into_value_with(&ruby))
131
163
  }
132
164
 
133
165
  pub fn get_bandit_action(
@@ -144,7 +176,7 @@ impl Client {
144
176
  .map_err(|err| {
145
177
  Error::new(
146
178
  exception::runtime_error(),
147
- format!("enexpected value for subject_attributes: {err}"),
179
+ format!("Unexpected value for subject_attributes: {err}"),
148
180
  )
149
181
  })?;
150
182
  let actions = serde_magnus::deserialize(actions)?;
@@ -14,8 +14,6 @@ pub(crate) const SDK_METADATA: SdkMetadata = SdkMetadata {
14
14
 
15
15
  #[magnus::init]
16
16
  fn init(ruby: &Ruby) -> Result<(), Error> {
17
- env_logger::Builder::from_env(env_logger::Env::new().default_filter_or("eppo=debug")).init();
18
-
19
17
  let eppo_client = ruby.define_module("EppoClient")?;
20
18
  let core = eppo_client.define_module("Core")?;
21
19
 
@@ -22,7 +22,7 @@ module EppoClient
22
22
  def init(config)
23
23
  config.validate
24
24
 
25
- if !@core.nil?
25
+ if @core
26
26
  STDERR.puts "Eppo Warning: multiple initialization of the client"
27
27
  @core.shutdown
28
28
  end
@@ -91,7 +91,10 @@ module EppoClient
91
91
  log_assignment(result[:assignment_event])
92
92
  log_bandit_action(result[:bandit_event])
93
93
 
94
- return {:variation => result[:variation], :action => result[:action]}
94
+ {
95
+ :variation => result[:variation],
96
+ :action => result[:action]
97
+ }
95
98
  end
96
99
 
97
100
  def get_bandit_action_details(flag_key, subject_key, subject_attributes, actions, default_variation)
@@ -102,7 +105,7 @@ module EppoClient
102
105
  log_assignment(result[:assignment_event])
103
106
  log_bandit_action(result[:bandit_event])
104
107
 
105
- return {
108
+ {
106
109
  :variation => result[:variation],
107
110
  :action => result[:action],
108
111
  :evaluationDetails => details
@@ -116,13 +119,11 @@ module EppoClient
116
119
  logger = Logger.new($stdout)
117
120
  begin
118
121
  assignment = @core.get_assignment(flag_key, subject_key, subject_attributes, expected_type)
119
- if not assignment then
120
- return default_value
121
- end
122
+ return default_value unless assignment
122
123
 
123
124
  log_assignment(assignment[:event])
124
125
 
125
- return assignment[:value][:value]
126
+ return assignment[:value]
126
127
  rescue StandardError => error
127
128
  logger.debug("[Eppo SDK] Failed to get assignment: #{error}")
128
129
 
@@ -137,19 +138,16 @@ module EppoClient
137
138
  result, event = @core.get_assignment_details(flag_key, subject_key, subject_attributes, expected_type)
138
139
  log_assignment(event)
139
140
 
140
- if not result[:variation] then
141
+ if !result[:variation]
141
142
  result[:variation] = default_value
142
- else
143
- # unwrap from AssignmentValue to untyped value
144
- result[:variation] = result[:variation][:value]
145
143
  end
146
144
 
147
- return result
145
+ result
148
146
  end
149
147
  # rubocop:enable Metrics/MethodLength
150
148
 
151
149
  def log_assignment(event)
152
- if not event then return end
150
+ return unless event
153
151
 
154
152
  # Because rust's AssignmentEvent has a #[flatten] extra_logging
155
153
  # field, serde_magnus serializes it as a normal HashMap with
@@ -170,7 +168,7 @@ module EppoClient
170
168
  end
171
169
 
172
170
  def log_bandit_action(event)
173
- if not event then return end
171
+ return unless event
174
172
 
175
173
  begin
176
174
  @assignment_logger.log_bandit_action(event)
@@ -6,14 +6,15 @@ require_relative "assignment_logger"
6
6
  module EppoClient
7
7
  # The class for configuring the Eppo client singleton
8
8
  class Config
9
- attr_reader :api_key, :assignment_logger, :base_url, :poll_interval_seconds, :poll_jitter_seconds
9
+ attr_reader :api_key, :assignment_logger, :base_url, :poll_interval_seconds, :poll_jitter_seconds, :log_level
10
10
 
11
- def initialize(api_key, assignment_logger: AssignmentLogger.new, base_url: EppoClient::Core::DEFAULT_BASE_URL, poll_interval_seconds: EppoClient::Core::DEFAULT_POLL_INTERVAL_SECONDS, poll_jitter_seconds: EppoClient::Core::DEFAULT_POLL_JITTER_SECONDS, initial_configuration: nil)
11
+ def initialize(api_key, assignment_logger: AssignmentLogger.new, base_url: EppoClient::Core::DEFAULT_BASE_URL, poll_interval_seconds: EppoClient::Core::DEFAULT_POLL_INTERVAL_SECONDS, poll_jitter_seconds: EppoClient::Core::DEFAULT_POLL_JITTER_SECONDS, initial_configuration: nil, log_level: nil)
12
12
  @api_key = api_key
13
13
  @assignment_logger = assignment_logger
14
14
  @base_url = base_url
15
15
  @poll_interval_seconds = poll_interval_seconds
16
16
  @poll_jitter_seconds = poll_jitter_seconds
17
+ @log_level = log_level
17
18
  end
18
19
 
19
20
  def validate
@@ -2,5 +2,5 @@
2
2
 
3
3
  # TODO: this version and ext/eppo_client/Cargo.toml should be in sync
4
4
  module EppoClient
5
- VERSION = "3.2.8"
5
+ VERSION = "3.4.0"
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eppo-server-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.8
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eppo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-31 00:00:00.000000000 Z
11
+ date: 2025-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb_sys
@@ -73,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
73
  - !ruby/object:Gem::Version
74
74
  version: 3.3.11
75
75
  requirements: []
76
- rubygems_version: 3.5.16
76
+ rubygems_version: 3.5.22
77
77
  signing_key:
78
78
  specification_version: 4
79
79
  summary: Eppo SDK for Ruby