shipeasy-sdk 2.3.1 → 2.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/lib/shipeasy/client.rb +27 -0
- data/lib/shipeasy/config.rb +12 -2
- data/lib/shipeasy/engine.rb +123 -70
- data/lib/shipeasy/logging.rb +79 -0
- data/lib/shipeasy/sdk/version.rb +1 -1
- data/lib/shipeasy-sdk.rb +3 -2
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 991e86824a2b5fb735a7d7f53dbff73c635f1a4adb5a3056e3b5c359a164dcbc
|
|
4
|
+
data.tar.gz: 897872d04e78e03dd76f26b5e82108a5a18ef7eea749cf9657ced843e63843c4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 540e78bcb95b819ad2a74c8d69387f5ed252b9b6fc9c12ca89f0aa9b1d6ff1653197394721f35931808abb89fa0a4bc002ce7f1c152857e5e80bfd6759bf2c8e
|
|
7
|
+
data.tar.gz: 36a1275319ef6958380451dd186e0dcd90cbdb04110b0cdf96e59a5e013d8a7a730456672325f9445eae0c4e2ac5f9ace72c936f9d039d9575e0fbd4c5933889
|
data/lib/shipeasy/client.rb
CHANGED
|
@@ -34,35 +34,62 @@ module Shipeasy
|
|
|
34
34
|
@attributes = engine.bind_attributes(mapped)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
# NOTE on fail-safe reads: the engine's runtime methods already never raise
|
|
38
|
+
# (each is wrapped in Engine#safe_run). The extra defensive rescue here is a
|
|
39
|
+
# belt-and-braces guard so even an unexpected failure BEFORE the engine call
|
|
40
|
+
# (e.g. an @attributes deref) still returns the documented safe default
|
|
41
|
+
# rather than propagating into product code.
|
|
42
|
+
|
|
37
43
|
def get_flag(name, default: false)
|
|
38
44
|
@engine.get_flag(name, @attributes, default: default)
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
Shipeasy::Logging.error "[shipeasy] Client#get_flag('#{name}') failed — returning default: #{e.message}"
|
|
47
|
+
default
|
|
39
48
|
end
|
|
40
49
|
|
|
41
50
|
def get_flag_detail(name)
|
|
42
51
|
@engine.get_flag_detail(name, @attributes)
|
|
52
|
+
rescue StandardError => e
|
|
53
|
+
Shipeasy::Logging.error "[shipeasy] Client#get_flag_detail('#{name}') failed — returning safe default: #{e.message}"
|
|
54
|
+
Shipeasy::Engine::FlagDetail.new(value: false, reason: Shipeasy::Engine::REASON_CLIENT_NOT_READY)
|
|
43
55
|
end
|
|
44
56
|
|
|
45
57
|
# Configs are not user-scoped, but exposed here for one-stop ergonomics.
|
|
46
58
|
def get_config(name, decode = nil, default: nil)
|
|
47
59
|
@engine.get_config(name, decode, default: default)
|
|
60
|
+
rescue StandardError => e
|
|
61
|
+
Shipeasy::Logging.error "[shipeasy] Client#get_config('#{name}') failed — returning default: #{e.message}"
|
|
62
|
+
default
|
|
48
63
|
end
|
|
49
64
|
|
|
50
65
|
def get_experiment(name, default_params, decode = nil)
|
|
51
66
|
@engine.get_experiment(name, @attributes, default_params, decode)
|
|
67
|
+
rescue StandardError => e
|
|
68
|
+
Shipeasy::Logging.error "[shipeasy] Client#get_experiment('#{name}') failed — returning safe default: #{e.message}"
|
|
69
|
+
Shipeasy::SDK::Eval::ExperimentResult.new(in_experiment: false, group: "control", params: default_params)
|
|
52
70
|
end
|
|
53
71
|
|
|
54
72
|
# Killswitches are not user-scoped; forwarded straight to the engine.
|
|
55
73
|
def get_killswitch(name, switch_key = nil)
|
|
56
74
|
@engine.get_killswitch(name, switch_key)
|
|
75
|
+
rescue StandardError => e
|
|
76
|
+
Shipeasy::Logging.error "[shipeasy] Client#get_killswitch('#{name}') failed — returning false: #{e.message}"
|
|
77
|
+
false
|
|
57
78
|
end
|
|
58
79
|
|
|
59
80
|
def track(event_name, props = {})
|
|
60
81
|
id = @attributes["user_id"] || @attributes["anonymous_id"]
|
|
61
82
|
@engine.track(id, event_name, props)
|
|
83
|
+
rescue StandardError => e
|
|
84
|
+
Shipeasy::Logging.error "[shipeasy] Client#track('#{event_name}') failed: #{e.message}"
|
|
85
|
+
nil
|
|
62
86
|
end
|
|
63
87
|
|
|
64
88
|
def log_exposure(experiment_name)
|
|
65
89
|
@engine.log_exposure(@attributes, experiment_name)
|
|
90
|
+
rescue StandardError => e
|
|
91
|
+
Shipeasy::Logging.error "[shipeasy] Client#log_exposure('#{experiment_name}') failed: #{e.message}"
|
|
92
|
+
nil
|
|
66
93
|
end
|
|
67
94
|
end
|
|
68
95
|
|
data/lib/shipeasy/config.rb
CHANGED
|
@@ -33,6 +33,14 @@ module Shipeasy
|
|
|
33
33
|
attr_accessor :env, :disable_telemetry, :telemetry_url,
|
|
34
34
|
:private_attributes, :sticky_store
|
|
35
35
|
|
|
36
|
+
# SDK-wide diagnostic verbosity for the leveled logger (Shipeasy::Logging).
|
|
37
|
+
# One of :silent, :error, :warn (default), :info, :debug (strings accepted
|
|
38
|
+
# and downcased; unknown falls back to :warn). Controls the stderr output of
|
|
39
|
+
# every caught-error diagnostic the SDK emits — the public runtime reads
|
|
40
|
+
# (get_flag/get_config/…) never raise, so this only tunes how loud they are
|
|
41
|
+
# about a recovered failure. Threaded into the global Engine by configure.
|
|
42
|
+
attr_accessor :log_level
|
|
43
|
+
|
|
36
44
|
# Fetch lifecycle for the global engine `configure` builds:
|
|
37
45
|
# - init (default true): fire a one-shot fetch fire-and-forget so the first
|
|
38
46
|
# `Shipeasy::Client.new(user).get_flag(...)` resolves against real rules
|
|
@@ -70,6 +78,7 @@ module Shipeasy
|
|
|
70
78
|
@telemetry_url = nil
|
|
71
79
|
@private_attributes = nil
|
|
72
80
|
@sticky_store = nil
|
|
81
|
+
@log_level = :warn
|
|
73
82
|
|
|
74
83
|
@profile = "default"
|
|
75
84
|
@default_chunk = "index"
|
|
@@ -147,6 +156,7 @@ module Shipeasy
|
|
|
147
156
|
telemetry_url: cfg.telemetry_url,
|
|
148
157
|
private_attributes: cfg.private_attributes,
|
|
149
158
|
sticky_store: cfg.sticky_store,
|
|
159
|
+
log_level: cfg.log_level,
|
|
150
160
|
)
|
|
151
161
|
@engine = engine
|
|
152
162
|
# Capture +engine+ in the closure (not the @engine ivar, which a concurrent
|
|
@@ -155,13 +165,13 @@ module Shipeasy
|
|
|
155
165
|
Thread.new do
|
|
156
166
|
engine.init # initial fetch + background poll thread
|
|
157
167
|
rescue => e
|
|
158
|
-
|
|
168
|
+
Shipeasy::Logging.error "[shipeasy] configure(poll) background poll failed: #{e.message}"
|
|
159
169
|
end
|
|
160
170
|
elsif cfg.init
|
|
161
171
|
Thread.new do
|
|
162
172
|
engine.init_once
|
|
163
173
|
rescue => e
|
|
164
|
-
|
|
174
|
+
Shipeasy::Logging.error "[shipeasy] configure() one-shot fetch failed: #{e.message}"
|
|
165
175
|
end
|
|
166
176
|
end
|
|
167
177
|
engine
|
data/lib/shipeasy/engine.rb
CHANGED
|
@@ -3,6 +3,7 @@ require "uri"
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "thread"
|
|
5
5
|
require "cgi"
|
|
6
|
+
require_relative "logging"
|
|
6
7
|
require_relative "sdk/eval"
|
|
7
8
|
require_relative "sdk/telemetry"
|
|
8
9
|
require_relative "sdk/anon_id"
|
|
@@ -33,7 +34,12 @@ module Shipeasy
|
|
|
33
34
|
# /sdk/i18n/loader.js) — distinct from the edge API the blobs are fetched from.
|
|
34
35
|
DEFAULT_CDN_BASE = "https://cdn.shipeasy.ai"
|
|
35
36
|
|
|
36
|
-
def initialize(api_key:, base_url: nil, env: "prod", disable_telemetry: false, telemetry_url: nil, test_mode: false, private_attributes: nil, sticky_store: nil)
|
|
37
|
+
def initialize(api_key:, base_url: nil, env: "prod", disable_telemetry: false, telemetry_url: nil, test_mode: false, private_attributes: nil, sticky_store: nil, log_level: nil)
|
|
38
|
+
# SDK-wide diagnostic verbosity. Set the leveled logger from the passed
|
|
39
|
+
# level (default :warn; unknown falls back to :warn). The logger is
|
|
40
|
+
# module-scoped, so the last-built engine wins — mirrors the TS SDK,
|
|
41
|
+
# where the last configure() sets the level.
|
|
42
|
+
Shipeasy::Logging.set_level(log_level || Shipeasy::Logging::DEFAULT_LEVEL)
|
|
37
43
|
@api_key = api_key
|
|
38
44
|
@base_url = (base_url || DEFAULT_BASE_URL).chomp("/")
|
|
39
45
|
# Read-env tag. Used by telemetry below and stamped onto see() error
|
|
@@ -205,6 +211,12 @@ module Shipeasy
|
|
|
205
211
|
# Evaluate a flag and return why. Telemetry ("gate" beacon) is emitted
|
|
206
212
|
# exactly once here (steps 2–5), never on the OVERRIDE short-circuit.
|
|
207
213
|
def get_flag_detail(name, user)
|
|
214
|
+
safe_run("get_flag_detail('#{name}')", FlagDetail.new(value: false, reason: REASON_CLIENT_NOT_READY)) do
|
|
215
|
+
get_flag_detail_inner(name, user)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def get_flag_detail_inner(name, user)
|
|
208
220
|
key = name.to_s
|
|
209
221
|
|
|
210
222
|
# 1. Override short-circuits before any telemetry (mirrors get_config).
|
|
@@ -233,31 +245,51 @@ module Shipeasy
|
|
|
233
245
|
end
|
|
234
246
|
|
|
235
247
|
def get_flag(name, user, default: false)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
248
|
+
safe_run("get_flag('#{name}')", default) do
|
|
249
|
+
detail = get_flag_detail(name, user)
|
|
250
|
+
if detail.reason == REASON_CLIENT_NOT_READY || detail.reason == REASON_FLAG_NOT_FOUND
|
|
251
|
+
default
|
|
252
|
+
else
|
|
253
|
+
detail.value
|
|
254
|
+
end
|
|
241
255
|
end
|
|
242
256
|
end
|
|
243
257
|
|
|
244
258
|
def get_config(name, decode = nil, default: nil)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
259
|
+
safe_run("get_config('#{name}')", default) do
|
|
260
|
+
key = name.to_s
|
|
261
|
+
has_override, override = @mutex.synchronize do
|
|
262
|
+
[@config_overrides.key?(key), @config_overrides[key]]
|
|
263
|
+
end
|
|
264
|
+
if has_override
|
|
265
|
+
begin
|
|
266
|
+
next(decode ? decode.call(override) : override)
|
|
267
|
+
rescue => e
|
|
268
|
+
Shipeasy::Logging.warn "[shipeasy] get_config('#{name}') decode failed: #{e.message}"
|
|
269
|
+
next default
|
|
270
|
+
end
|
|
271
|
+
end
|
|
252
272
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
273
|
+
@telemetry.emit("config", name)
|
|
274
|
+
entry = @mutex.synchronize { @flags_blob&.dig("configs", name) }
|
|
275
|
+
next default unless entry
|
|
276
|
+
value = entry["value"]
|
|
277
|
+
begin
|
|
278
|
+
decode ? decode.call(value) : value
|
|
279
|
+
rescue => e
|
|
280
|
+
Shipeasy::Logging.warn "[shipeasy] get_config('#{name}') decode failed: #{e.message}"
|
|
281
|
+
default
|
|
282
|
+
end
|
|
283
|
+
end
|
|
258
284
|
end
|
|
259
285
|
|
|
260
286
|
def get_experiment(name, user, default_params, decode = nil)
|
|
287
|
+
safe_run("get_experiment('#{name}')", Eval::ExperimentResult.new(in_experiment: false, group: "control", params: default_params)) do
|
|
288
|
+
get_experiment_inner(name, user, default_params, decode)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def get_experiment_inner(name, user, default_params, decode = nil)
|
|
261
293
|
key = name.to_s
|
|
262
294
|
override = @mutex.synchronize { @exp_overrides[key] }
|
|
263
295
|
if override
|
|
@@ -287,7 +319,7 @@ module Shipeasy
|
|
|
287
319
|
params: decode.call(result.params),
|
|
288
320
|
)
|
|
289
321
|
rescue => e
|
|
290
|
-
warn "[shipeasy] get_experiment('#{name}') decode failed: #{e.message}"
|
|
322
|
+
Shipeasy::Logging.warn "[shipeasy] get_experiment('#{name}') decode failed: #{e.message}"
|
|
291
323
|
return Eval::ExperimentResult.new(in_experiment: false, group: "control", params: default_params)
|
|
292
324
|
end
|
|
293
325
|
end
|
|
@@ -309,16 +341,18 @@ module Shipeasy
|
|
|
309
341
|
# value (so an unconfigured key behaves exactly like the no-key call).
|
|
310
342
|
# Unknown killswitches return false. Not user-scoped.
|
|
311
343
|
def get_killswitch(name, switch_key = nil)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
344
|
+
safe_run("get_killswitch('#{name}')", false) do
|
|
345
|
+
@telemetry.emit("ks", name)
|
|
346
|
+
ks = @mutex.synchronize { @flags_blob&.dig("killswitches", name.to_s) }
|
|
347
|
+
next false unless ks
|
|
348
|
+
unless switch_key.nil?
|
|
349
|
+
switches = ks["switches"] || {}
|
|
350
|
+
key = switch_key.to_s
|
|
351
|
+
next Eval.enabled?(switches[key]) if switches.key?(key)
|
|
352
|
+
# key not configured → fall through to the top-level value
|
|
353
|
+
end
|
|
354
|
+
Eval.enabled?(ks["killed"])
|
|
320
355
|
end
|
|
321
|
-
Eval.enabled?(ks["killed"])
|
|
322
356
|
end
|
|
323
357
|
|
|
324
358
|
# Batch-evaluate every loaded gate, config and experiment for +user+ into
|
|
@@ -387,24 +421,27 @@ module Shipeasy
|
|
|
387
421
|
end
|
|
388
422
|
|
|
389
423
|
def track(user_id, event_name, props = {})
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
424
|
+
safe_run("track('#{event_name}')", nil) do
|
|
425
|
+
next if @test_mode
|
|
426
|
+
|
|
427
|
+
safe_props = strip_private(props)
|
|
428
|
+
|
|
429
|
+
payload = JSON.generate({
|
|
430
|
+
events: [{
|
|
431
|
+
type: "metric",
|
|
432
|
+
event_name: event_name,
|
|
433
|
+
user_id: user_id.to_s,
|
|
434
|
+
ts: (Time.now.to_f * 1000).to_i,
|
|
435
|
+
**(safe_props.empty? ? {} : { properties: safe_props }),
|
|
436
|
+
}],
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
Thread.new do
|
|
440
|
+
post("/collect", payload)
|
|
441
|
+
rescue => e
|
|
442
|
+
Shipeasy::Logging.warn "[shipeasy] track failed: #{e.message}"
|
|
443
|
+
end
|
|
444
|
+
nil
|
|
408
445
|
end
|
|
409
446
|
end
|
|
410
447
|
|
|
@@ -415,27 +452,30 @@ module Shipeasy
|
|
|
415
452
|
# string is wrapped as { "user_id" => id }); if enrolled, POSTs a single
|
|
416
453
|
# exposure to /collect. No-op in test mode or when the user isn't enrolled.
|
|
417
454
|
def log_exposure(user_or_user_id, experiment_name)
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
455
|
+
safe_run("log_exposure('#{experiment_name}')", nil) do
|
|
456
|
+
next if @test_mode
|
|
457
|
+
|
|
458
|
+
user = user_or_user_id.is_a?(Hash) ? user_or_user_id : { "user_id" => user_or_user_id.to_s }
|
|
459
|
+
result = get_experiment(experiment_name, user, {})
|
|
460
|
+
next unless result.in_experiment
|
|
461
|
+
|
|
462
|
+
u = user.transform_keys(&:to_s)
|
|
463
|
+
payload = JSON.generate({
|
|
464
|
+
events: [{
|
|
465
|
+
type: "exposure",
|
|
466
|
+
experiment: experiment_name.to_s,
|
|
467
|
+
group: result.group,
|
|
468
|
+
user_id: (u["user_id"] || u["anonymous_id"]).to_s,
|
|
469
|
+
ts: (Time.now.to_f * 1000).to_i,
|
|
470
|
+
}],
|
|
471
|
+
})
|
|
434
472
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
473
|
+
Thread.new do
|
|
474
|
+
post("/collect", payload)
|
|
475
|
+
rescue => e
|
|
476
|
+
Shipeasy::Logging.warn "[shipeasy] log_exposure failed: #{e.message}"
|
|
477
|
+
end
|
|
478
|
+
nil
|
|
439
479
|
end
|
|
440
480
|
end
|
|
441
481
|
|
|
@@ -466,6 +506,19 @@ module Shipeasy
|
|
|
466
506
|
|
|
467
507
|
private
|
|
468
508
|
|
|
509
|
+
# Last-resort guard that makes a public RUNTIME method (get_flag /
|
|
510
|
+
# get_config / get_experiment / get_killswitch / track / log_exposure)
|
|
511
|
+
# unable to raise into product code, even if an internal invariant is
|
|
512
|
+
# violated. Runs the block; on any StandardError it logs at :error and
|
|
513
|
+
# returns +fallback+ (the method's documented safe default). +label+ names
|
|
514
|
+
# the method for the log line.
|
|
515
|
+
def safe_run(label, fallback)
|
|
516
|
+
yield
|
|
517
|
+
rescue StandardError => e
|
|
518
|
+
Shipeasy::Logging.error "[shipeasy] #{label} failed — returning safe default: #{e.message}"
|
|
519
|
+
fallback
|
|
520
|
+
end
|
|
521
|
+
|
|
469
522
|
# Build the wire event and fire-and-forget POST it to /collect. No-op in
|
|
470
523
|
# test mode (mirrors track). Spam-guarded. Never raises into caller code.
|
|
471
524
|
def dispatch_see(built)
|
|
@@ -485,10 +538,10 @@ module Shipeasy
|
|
|
485
538
|
Thread.new do
|
|
486
539
|
post("/collect", payload)
|
|
487
540
|
rescue => e
|
|
488
|
-
warn "[shipeasy] see() send failed: #{e.message}"
|
|
541
|
+
Shipeasy::Logging.warn "[shipeasy] see() send failed: #{e.message}"
|
|
489
542
|
end
|
|
490
543
|
rescue => e
|
|
491
|
-
|
|
544
|
+
Shipeasy::Logging.error "[shipeasy] see() failed: #{e.message}"
|
|
492
545
|
end
|
|
493
546
|
|
|
494
547
|
# Drop caller-marked private attributes from an outbound props bag. Handles
|
|
@@ -519,7 +572,7 @@ module Shipeasy
|
|
|
519
572
|
begin
|
|
520
573
|
listener.call
|
|
521
574
|
rescue => e
|
|
522
|
-
warn "[shipeasy] on_change listener raised: #{e.message}"
|
|
575
|
+
Shipeasy::Logging.warn "[shipeasy] on_change listener raised: #{e.message}"
|
|
523
576
|
end
|
|
524
577
|
end
|
|
525
578
|
end
|
|
@@ -557,7 +610,7 @@ module Shipeasy
|
|
|
557
610
|
begin
|
|
558
611
|
fetch_all
|
|
559
612
|
rescue => e
|
|
560
|
-
|
|
613
|
+
Shipeasy::Logging.error "[shipeasy] background poll failed: #{e.message}"
|
|
561
614
|
end
|
|
562
615
|
end
|
|
563
616
|
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Leveled logger shared by the whole gem.
|
|
2
|
+
#
|
|
3
|
+
# Every diagnostic the SDK emits from a *caught* error goes through here, so a
|
|
4
|
+
# single `log_level` config option (default :warn) controls the SDK's stderr
|
|
5
|
+
# output. The contract for the SDK's public RUNTIME methods (get_flag,
|
|
6
|
+
# get_config, get_experiment, get_killswitch, track, log_exposure, see, …) is
|
|
7
|
+
# that they NEVER raise into product code — so logging itself is best-effort
|
|
8
|
+
# too: a broken/throwing $stderr can never take down a flag read.
|
|
9
|
+
#
|
|
10
|
+
# Level ordering (a message at level L is emitted iff the configured level is at
|
|
11
|
+
# least as verbose as L):
|
|
12
|
+
# silent < error < warn < info < debug
|
|
13
|
+
# :warn (the default) therefore emits `error` + `warn` and suppresses the
|
|
14
|
+
# informational `info` / `debug` chatter.
|
|
15
|
+
|
|
16
|
+
module Shipeasy
|
|
17
|
+
module Logging
|
|
18
|
+
# All accepted levels, in increasing verbosity.
|
|
19
|
+
LEVELS = %i[silent error warn info debug].freeze
|
|
20
|
+
|
|
21
|
+
RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 }.freeze
|
|
22
|
+
|
|
23
|
+
DEFAULT_LEVEL = :warn
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
# Set the SDK-wide log level. Accepts a symbol or string (case-insensitive);
|
|
27
|
+
# an unrecognised value falls back to the default :warn so a bad config value
|
|
28
|
+
# can never silence a genuine error unexpectedly.
|
|
29
|
+
def set_level(level)
|
|
30
|
+
@level = normalize(level)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The current SDK-wide log level (defaults to :warn until set).
|
|
34
|
+
def level
|
|
35
|
+
@level || DEFAULT_LEVEL
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Normalise any input to a known level symbol; unknown → DEFAULT_LEVEL.
|
|
39
|
+
def normalize(level)
|
|
40
|
+
sym =
|
|
41
|
+
case level
|
|
42
|
+
when Symbol then level.to_s.downcase.to_sym
|
|
43
|
+
when String then level.downcase.to_sym
|
|
44
|
+
else nil
|
|
45
|
+
end
|
|
46
|
+
RANK.key?(sym) ? sym : DEFAULT_LEVEL
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def error(msg)
|
|
50
|
+
emit(:error, msg)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def warn(msg)
|
|
54
|
+
emit(:warn, msg)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def info(msg)
|
|
58
|
+
emit(:info, msg)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def debug(msg)
|
|
62
|
+
emit(:debug, msg)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
# Gate on the configured level, then write to $stderr exactly as the old
|
|
68
|
+
# bare `warn "[shipeasy] …"` calls did (Kernel#warn → $stderr). Never
|
|
69
|
+
# raises: a broken stream is swallowed.
|
|
70
|
+
def emit(msg_level, msg)
|
|
71
|
+
return if RANK[level] < RANK[msg_level]
|
|
72
|
+
|
|
73
|
+
Kernel.warn(msg)
|
|
74
|
+
rescue StandardError
|
|
75
|
+
# logging must never raise into product code
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/shipeasy/sdk/version.rb
CHANGED
data/lib/shipeasy-sdk.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require_relative "shipeasy/sdk/version"
|
|
2
|
+
require_relative "shipeasy/logging"
|
|
2
3
|
require_relative "shipeasy/config"
|
|
3
4
|
require_relative "shipeasy/sdk/murmur3"
|
|
4
5
|
require_relative "shipeasy/sdk/eval"
|
|
@@ -54,7 +55,7 @@ module Shipeasy
|
|
|
54
55
|
def self.see(problem)
|
|
55
56
|
client = default_client
|
|
56
57
|
if client.nil?
|
|
57
|
-
warn "[shipeasy] see() called before a client was created — error dropped"
|
|
58
|
+
Shipeasy::Logging.warn "[shipeasy] see() called before a client was created — error dropped"
|
|
58
59
|
return See::NullChain.new
|
|
59
60
|
end
|
|
60
61
|
client.see(problem)
|
|
@@ -64,7 +65,7 @@ module Shipeasy
|
|
|
64
65
|
def self.see_violation(name)
|
|
65
66
|
client = default_client
|
|
66
67
|
if client.nil?
|
|
67
|
-
warn "[shipeasy] see_violation() called before a client was created — error dropped"
|
|
68
|
+
Shipeasy::Logging.warn "[shipeasy] see_violation() called before a client was created — error dropped"
|
|
68
69
|
return See::NullChain.new
|
|
69
70
|
end
|
|
70
71
|
client.see_violation(name)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shipeasy-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shipeasy, Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rspec
|
|
@@ -140,6 +140,7 @@ files:
|
|
|
140
140
|
- lib/shipeasy/i18n/label_fetcher.rb
|
|
141
141
|
- lib/shipeasy/i18n/railtie.rb
|
|
142
142
|
- lib/shipeasy/i18n/view_helpers.rb
|
|
143
|
+
- lib/shipeasy/logging.rb
|
|
143
144
|
- lib/shipeasy/sdk/anon_id.rb
|
|
144
145
|
- lib/shipeasy/sdk/eval.rb
|
|
145
146
|
- lib/shipeasy/sdk/murmur3.rb
|