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.
- checksums.yaml +4 -4
- data/Cargo.lock +668 -392
- data/README.md +49 -0
- data/ext/eppo_client/Cargo.toml +3 -3
- data/ext/eppo_client/src/client.rs +39 -7
- data/ext/eppo_client/src/lib.rs +0 -2
- data/lib/eppo_client/client.rb +12 -14
- data/lib/eppo_client/config.rb +3 -2
- data/lib/eppo_client/version.rb +1 -1
- metadata +3 -3
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`
|
data/ext/eppo_client/Cargo.toml
CHANGED
@@ -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.
|
4
|
+
version = "3.4.0"
|
5
5
|
edition = "2021"
|
6
6
|
license = "MIT"
|
7
7
|
publish = false
|
8
|
-
rust-version = "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 = "
|
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
|
-
&
|
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 =
|
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(
|
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(
|
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!("
|
179
|
+
format!("Unexpected value for subject_attributes: {err}"),
|
148
180
|
)
|
149
181
|
})?;
|
150
182
|
let actions = serde_magnus::deserialize(actions)?;
|
data/ext/eppo_client/src/lib.rs
CHANGED
@@ -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
|
|
data/lib/eppo_client/client.rb
CHANGED
@@ -22,7 +22,7 @@ module EppoClient
|
|
22
22
|
def init(config)
|
23
23
|
config.validate
|
24
24
|
|
25
|
-
if
|
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
|
-
|
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
|
-
|
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
|
-
|
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]
|
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
|
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
|
-
|
145
|
+
result
|
148
146
|
end
|
149
147
|
# rubocop:enable Metrics/MethodLength
|
150
148
|
|
151
149
|
def log_assignment(event)
|
152
|
-
|
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
|
-
|
171
|
+
return unless event
|
174
172
|
|
175
173
|
begin
|
176
174
|
@assignment_logger.log_bandit_action(event)
|
data/lib/eppo_client/config.rb
CHANGED
@@ -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
|
data/lib/eppo_client/version.rb
CHANGED
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.
|
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:
|
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.
|
76
|
+
rubygems_version: 3.5.22
|
77
77
|
signing_key:
|
78
78
|
specification_version: 4
|
79
79
|
summary: Eppo SDK for Ruby
|