valkey-glide-rb 0.9.0 → 0.9.1
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/.rubocop.yml +1 -0
- data/AGENTS.md +8 -8
- data/CHANGELOG.md +8 -0
- data/CLAUDE.md +3 -3
- data/CONTRIBUTING.md +2 -2
- data/DEVELOPER.md +35 -19
- data/README.md +89 -2
- data/Rakefile +15 -5
- data/THIRD_PARTY_LICENSES_RUBY +170820 -0
- data/examples/opentelemetry.rb +20 -1
- data/lib/valkey/bindings.rb +33 -1
- data/lib/valkey/commands/cluster_commands.rb +20 -3
- data/lib/valkey/commands/connection_commands.rb +14 -5
- data/lib/valkey/commands/generic_commands.rb +110 -10
- data/lib/valkey/commands/server_commands.rb +12 -6
- data/lib/valkey/native/aarch64-apple-darwin/libglide_ffi.dylib +0 -0
- data/lib/valkey/native/aarch64-unknown-linux-gnu/libglide_ffi.so +0 -0
- data/lib/valkey/native/aarch64-unknown-linux-musl/libglide_ffi.so +0 -0
- data/lib/valkey/native/x86_64-unknown-linux-gnu/libglide_ffi.so +0 -0
- data/lib/valkey/native/x86_64-unknown-linux-musl/libglide_ffi.so +0 -0
- data/lib/valkey/opentelemetry.rb +79 -1
- data/lib/valkey/read_from.rb +24 -0
- data/lib/valkey/version.rb +1 -1
- data/lib/valkey.rb +443 -378
- metadata +8 -2
data/examples/opentelemetry.rb
CHANGED
|
@@ -41,10 +41,29 @@ end
|
|
|
41
41
|
client.del("otel_key", "otel_pipe_1")
|
|
42
42
|
client.close
|
|
43
43
|
|
|
44
|
+
# Distributed tracing: register a parent_span_context_provider so command spans
|
|
45
|
+
# become children of the app's current trace instead of independent root spans.
|
|
46
|
+
# Here we hand-roll a fake W3C trace context (no dependency on the real
|
|
47
|
+
# opentelemetry-ruby gem); in a real app this would read from
|
|
48
|
+
# ::OpenTelemetry::Trace.current_span.context (see README).
|
|
49
|
+
fake_trace_id = "4bf92f3577b34da6a3ce929d0e0e4736"
|
|
50
|
+
fake_span_id = "00f067aa0ba902b7"
|
|
51
|
+
|
|
52
|
+
Valkey::OpenTelemetry.set_parent_span_context_provider do
|
|
53
|
+
{ trace_id: fake_trace_id, span_id: fake_span_id, trace_flags: 1, tracestate: nil }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
client = Valkey.new(host: host, port: port)
|
|
57
|
+
client.set("otel_traced_key", "value") # span will be a child of fake_trace_id/fake_span_id
|
|
58
|
+
client.close
|
|
59
|
+
|
|
60
|
+
Valkey::OpenTelemetry.set_parent_span_context_provider(nil)
|
|
61
|
+
|
|
44
62
|
# Allow exporter flush
|
|
45
63
|
sleep 2
|
|
46
64
|
|
|
47
65
|
puts "OpenTelemetry initialized: #{Valkey::OpenTelemetry.initialized?}"
|
|
48
66
|
puts "Traces file: #{TRACES_FILE} (#{File.size?(TRACES_FILE) || 0} bytes)"
|
|
49
67
|
puts "Metrics file: #{METRICS_FILE} (#{File.size?(METRICS_FILE) || 0} bytes)"
|
|
50
|
-
puts "Done."
|
|
68
|
+
puts "Done. Inspect #{TRACES_FILE} - the SET span for otel_traced_key should have " \
|
|
69
|
+
"trace_id=#{fake_trace_id} and parent_span_id=#{fake_span_id}."
|
data/lib/valkey/bindings.rb
CHANGED
|
@@ -4,6 +4,23 @@ class Valkey
|
|
|
4
4
|
module Bindings
|
|
5
5
|
extend FFI::Library
|
|
6
6
|
|
|
7
|
+
# Detect whether the current Linux system uses musl libc (e.g., Alpine Linux).
|
|
8
|
+
# Uses a three-check cascade — any single match is sufficient.
|
|
9
|
+
def self.musl_libc?
|
|
10
|
+
return false unless FFI::Platform::OS == "linux"
|
|
11
|
+
|
|
12
|
+
# Check 1: RUBY_PLATFORM contains 'musl' (e.g., Alpine-built Ruby)
|
|
13
|
+
return true if RUBY_PLATFORM.include?("musl")
|
|
14
|
+
|
|
15
|
+
# Check 2: /etc/alpine-release exists (Alpine Linux indicator)
|
|
16
|
+
return true if File.exist?("/etc/alpine-release")
|
|
17
|
+
|
|
18
|
+
# Check 3: RbConfig target_os contains 'musl'
|
|
19
|
+
return true if RbConfig::CONFIG["target_os"].to_s.include?("musl")
|
|
20
|
+
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
|
|
7
24
|
# Determine platform-specific library extension and directory name
|
|
8
25
|
def self.platform_info
|
|
9
26
|
os = if FFI::Platform.mac?
|
|
@@ -40,7 +57,7 @@ class Valkey
|
|
|
40
57
|
when "darwin"
|
|
41
58
|
"#{arch}-apple-darwin"
|
|
42
59
|
when "linux"
|
|
43
|
-
"#{arch}-unknown-linux-gnu"
|
|
60
|
+
musl_libc? ? "#{arch}-unknown-linux-musl" : "#{arch}-unknown-linux-gnu"
|
|
44
61
|
when "windows"
|
|
45
62
|
"#{arch}-pc-windows-msvc"
|
|
46
63
|
end
|
|
@@ -309,6 +326,21 @@ class Valkey
|
|
|
309
326
|
|
|
310
327
|
attach_function :create_batch_otel_span, [], :uint64 # returns span pointer (u64) or 0 on failure
|
|
311
328
|
|
|
329
|
+
attach_function :create_otel_span_with_trace_context, [
|
|
330
|
+
:int, # request_type (RequestType enum value)
|
|
331
|
+
:string, # trace_id (32-char lowercase hex, or nil for an independent span)
|
|
332
|
+
:string, # span_id (16-char lowercase hex, or nil for an independent span)
|
|
333
|
+
:uint8, # trace_flags
|
|
334
|
+
:string # trace_state (W3C tracestate header, or nil)
|
|
335
|
+
], :uint64 # returns span pointer (u64); falls back to an independent span on invalid context
|
|
336
|
+
|
|
337
|
+
attach_function :create_batch_otel_span_with_trace_context, [
|
|
338
|
+
:string, # trace_id (32-char lowercase hex, or nil for an independent span)
|
|
339
|
+
:string, # span_id (16-char lowercase hex, or nil for an independent span)
|
|
340
|
+
:uint8, # trace_flags
|
|
341
|
+
:string # trace_state (W3C tracestate header, or nil)
|
|
342
|
+
], :uint64 # returns span pointer (u64); falls back to an independent batch span on invalid context
|
|
343
|
+
|
|
312
344
|
attach_function :drop_otel_span, [
|
|
313
345
|
:uint64 # span_ptr to close
|
|
314
346
|
], :void
|
|
@@ -71,13 +71,30 @@ class Valkey
|
|
|
71
71
|
send_command(RequestType::CLUSTER_DEL_SLOTS_RANGE, [start_slot, end_slot])
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
# Valid modes for CLUSTER FAILOVER: FORCE, TAKEOVER.
|
|
75
|
+
CLUSTER_FAILOVER_MODES = %i[force takeover].freeze
|
|
76
|
+
|
|
74
77
|
# Force a failover of the cluster.
|
|
75
78
|
#
|
|
76
|
-
#
|
|
79
|
+
# Must be sent to a replica node. Without a mode, performs a coordinated
|
|
80
|
+
# failover (the primary is asked to pause clients and hand over). +:force+
|
|
81
|
+
# promotes the replica without coordinating with the primary (useful when
|
|
82
|
+
# the primary is unreachable); +:takeover+ promotes it without any cluster
|
|
83
|
+
# consensus (most aggressive — risks data loss / split brain).
|
|
84
|
+
#
|
|
85
|
+
# @param [Symbol, nil] mode optional failover mode: +:force+ or +:takeover+
|
|
77
86
|
# @return [String] `"OK"`
|
|
78
|
-
|
|
87
|
+
# @raise [ArgumentError] if +mode+ is not nil, +:force+, or +:takeover+
|
|
88
|
+
# @see https://valkey.io/commands/cluster-failover/
|
|
89
|
+
def cluster_failover(mode = nil)
|
|
79
90
|
args = []
|
|
80
|
-
|
|
91
|
+
unless mode.nil?
|
|
92
|
+
unless CLUSTER_FAILOVER_MODES.include?(mode)
|
|
93
|
+
raise ArgumentError, "invalid CLUSTER FAILOVER mode #{mode.inspect}: expected :force or :takeover"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
args << mode.to_s.upcase
|
|
97
|
+
end
|
|
81
98
|
send_command(RequestType::CLUSTER_FAILOVER, args)
|
|
82
99
|
end
|
|
83
100
|
|
|
@@ -94,7 +94,7 @@ class Valkey
|
|
|
94
94
|
# client(:set_name, "my_app") # => "OK"
|
|
95
95
|
# client(:list) # => [{"id" => "1", ...}, ...]
|
|
96
96
|
def client(subcommand, *args)
|
|
97
|
-
|
|
97
|
+
public_send("client_#{subcommand.to_s.downcase}", *args)
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
# Get the current client's ID.
|
|
@@ -136,8 +136,10 @@ class Valkey
|
|
|
136
136
|
|
|
137
137
|
send_command(RequestType::CLIENT_LIST, args) do |reply|
|
|
138
138
|
reply.lines.map do |line|
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
line.chomp.split.each_with_object({}) do |pair, hash|
|
|
140
|
+
key, value = pair.split("=", 2)
|
|
141
|
+
hash[key] = value || ""
|
|
142
|
+
end
|
|
141
143
|
end
|
|
142
144
|
end
|
|
143
145
|
end
|
|
@@ -290,6 +292,12 @@ class Valkey
|
|
|
290
292
|
send_command(RequestType::CLIENT_NO_TOUCH, [mode.to_s.upcase])
|
|
291
293
|
end
|
|
292
294
|
|
|
295
|
+
# Client-side caching commands are intentionally private.
|
|
296
|
+
#
|
|
297
|
+
# GLIDE's managed client-side caching is not yet implemented in the Ruby client
|
|
298
|
+
# These commands are not meant to be called directly and will be kept private.
|
|
299
|
+
private :client_tracking, :client_caching, :client_tracking_info, :client_getredir
|
|
300
|
+
|
|
293
301
|
private
|
|
294
302
|
|
|
295
303
|
def build_client_tracking_args(options)
|
|
@@ -299,8 +307,9 @@ class Valkey
|
|
|
299
307
|
when :redirect
|
|
300
308
|
args << "REDIRECT" << value.to_s
|
|
301
309
|
when :prefix
|
|
302
|
-
|
|
303
|
-
|
|
310
|
+
# Valkey requires the PREFIX keyword before each prefix value,
|
|
311
|
+
# e.g. PREFIX foo PREFIX bar.
|
|
312
|
+
Array(value).each { |prefix| args << "PREFIX" << prefix }
|
|
304
313
|
when :bcast
|
|
305
314
|
args << "BCAST" if value
|
|
306
315
|
when :optin
|
|
@@ -9,6 +9,11 @@ class Valkey
|
|
|
9
9
|
module GenericCommands
|
|
10
10
|
# Scan the keyspace
|
|
11
11
|
#
|
|
12
|
+
# @note Standalone mode only. glide-core has no defined default route for
|
|
13
|
+
# SCAN in cluster mode, so each call may land on a different node with
|
|
14
|
+
# no cursor continuity between them — results are undefined (missed or
|
|
15
|
+
# duplicated keys) rather than merely partial.
|
|
16
|
+
#
|
|
12
17
|
# @example Retrieve the first batch of keys
|
|
13
18
|
# valkey.scan(0)
|
|
14
19
|
# # => ["4", ["key:21", "key:47", "key:42"]]
|
|
@@ -33,6 +38,9 @@ class Valkey
|
|
|
33
38
|
|
|
34
39
|
# Scan the keyspace
|
|
35
40
|
#
|
|
41
|
+
# @note Standalone mode only. Built on {#scan}, which has undefined
|
|
42
|
+
# routing behavior in cluster mode (see its note).
|
|
43
|
+
#
|
|
36
44
|
# @example Retrieve all of the keys (with possible duplicates)
|
|
37
45
|
# valkey.scan_each.to_a
|
|
38
46
|
# # => ["key:21", "key:47", "key:42"]
|
|
@@ -52,16 +60,16 @@ class Valkey
|
|
|
52
60
|
#
|
|
53
61
|
# @return [Enumerator] an enumerator for all found keys
|
|
54
62
|
#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
def scan_each(**options, &block)
|
|
64
|
+
return to_enum(:scan_each, **options) unless block_given?
|
|
65
|
+
|
|
66
|
+
cursor = 0
|
|
67
|
+
loop do
|
|
68
|
+
cursor, keys = scan(cursor, **options)
|
|
69
|
+
keys.each(&block)
|
|
70
|
+
break if cursor == "0"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
65
73
|
|
|
66
74
|
# Remove the expiration from a key.
|
|
67
75
|
#
|
|
@@ -520,6 +528,98 @@ class Valkey
|
|
|
520
528
|
|
|
521
529
|
send_command(command, args, &block)
|
|
522
530
|
end
|
|
531
|
+
|
|
532
|
+
# Send any command as plain arguments and get the raw reply back, with no
|
|
533
|
+
# per-command method needed. Escape hatch for commands without a dedicated
|
|
534
|
+
# method yet, matching `redis-client`'s `#call`.
|
|
535
|
+
#
|
|
536
|
+
# @example Basic dispatch
|
|
537
|
+
# valkey.call("SET", "mykey", "value")
|
|
538
|
+
# # => "OK"
|
|
539
|
+
# @example Integers/Floats auto-stringify
|
|
540
|
+
# valkey.call("SET", "mykey", 42)
|
|
541
|
+
# # equivalent to call("SET", "mykey", "42")
|
|
542
|
+
# @example Arrays flatten
|
|
543
|
+
# valkey.call("LPUSH", "list", [1, 2, 3])
|
|
544
|
+
# # equivalent to call("LPUSH", "list", "1", "2", "3")
|
|
545
|
+
# @example Hashes flatten to alternating key/value
|
|
546
|
+
# valkey.call("HMSET", "hash", { "foo" => "1" })
|
|
547
|
+
# # equivalent to call("HMSET", "hash", "foo", "1")
|
|
548
|
+
# @example Keyword args become command flags; falsy/nil flags are dropped
|
|
549
|
+
# valkey.call("SET", "k", "v", nx: true, ex: 60)
|
|
550
|
+
# # equivalent to call("SET", "k", "v", "NX", "EX", "60")
|
|
551
|
+
#
|
|
552
|
+
# @param [Array<String, Integer, Float, Array, Hash>] argv command name and its arguments
|
|
553
|
+
# @param [Hash] kwargs trailing command flags; truthy values emit the upcased flag name,
|
|
554
|
+
# non-boolean values also emit the stringified value; falsy/nil values are dropped
|
|
555
|
+
# @return [Object] the raw reply, with no type-casting based on the command name
|
|
556
|
+
#
|
|
557
|
+
# @see https://valkey.io/commands/
|
|
558
|
+
def call(*argv, **kwargs)
|
|
559
|
+
send_command(RequestType::CUSTOM_COMMAND, flatten_call_args(argv).concat(call_flags(kwargs)))
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
# Send any command as a single Array of arguments and get the raw reply back.
|
|
563
|
+
# Same as {#call} but takes the whole command as one Array instead of splatted
|
|
564
|
+
# args, useful when the command is built dynamically. Matches `redis-client`'s
|
|
565
|
+
# `#call_v` — no keyword flags.
|
|
566
|
+
#
|
|
567
|
+
# @example
|
|
568
|
+
# valkey.call_v(["MGET"] + keys)
|
|
569
|
+
#
|
|
570
|
+
# @param [Array<String, Integer, Float, Array, Hash>] argv command name and its arguments
|
|
571
|
+
# @return [Object] the raw reply, with no type-casting based on the command name
|
|
572
|
+
#
|
|
573
|
+
# @see https://valkey.io/commands/
|
|
574
|
+
def call_v(argv)
|
|
575
|
+
send_command(RequestType::CUSTOM_COMMAND, flatten_call_args(argv))
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
private
|
|
579
|
+
|
|
580
|
+
# Flattens Arrays and Hashes (to alternating key/value) and stringifies
|
|
581
|
+
# Integers/Floats, matching `redis-client`'s documented `#call`/`#call_v` behavior.
|
|
582
|
+
#
|
|
583
|
+
# @example
|
|
584
|
+
# flatten_call_args(["CMD", [1, [2, 3]], { "a" => 1, "b" => [2, 3] }])
|
|
585
|
+
# # => ["CMD", "1", "2", "3", "a", "1", "b", "2", "3"]
|
|
586
|
+
def flatten_call_args(args)
|
|
587
|
+
acc = []
|
|
588
|
+
# Ruby doesn't have a built in queue, so we use a reversed stack instead
|
|
589
|
+
# Insertions into the stack needs to be reversed to maintain the Queue LIFO
|
|
590
|
+
# ordering
|
|
591
|
+
stack = args.reverse
|
|
592
|
+
|
|
593
|
+
until stack.empty?
|
|
594
|
+
arg = stack.pop
|
|
595
|
+
case arg
|
|
596
|
+
when Array
|
|
597
|
+
stack.concat(arg.reverse)
|
|
598
|
+
when Hash
|
|
599
|
+
arg.to_a.reverse_each { |pair| stack.concat(pair.reverse) }
|
|
600
|
+
else
|
|
601
|
+
acc << arg.to_s
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
acc
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
# Converts `call`'s **kwargs into trailing command flags: a truthy value emits
|
|
609
|
+
# the upcased flag name, and a non-boolean truthy value also emits the
|
|
610
|
+
# stringified value. Falsy/nil values are dropped entirely, not stringified.
|
|
611
|
+
#
|
|
612
|
+
# @example
|
|
613
|
+
# call_flags(nx: true, ex: 60, cond: false, ttl: nil)
|
|
614
|
+
# # => ["NX", "EX", "60"]
|
|
615
|
+
def call_flags(kwargs)
|
|
616
|
+
kwargs.each_with_object([]) do |(name, value), flags|
|
|
617
|
+
next unless value
|
|
618
|
+
|
|
619
|
+
flags << name.to_s.upcase
|
|
620
|
+
flags << value.to_s unless value == true
|
|
621
|
+
end
|
|
622
|
+
end
|
|
523
623
|
end
|
|
524
624
|
end
|
|
525
625
|
end
|
|
@@ -499,13 +499,19 @@ class Valkey
|
|
|
499
499
|
# @see https://valkey.io/commands/failover/
|
|
500
500
|
def failover(to: nil, force: false, abort: false, timeout: nil)
|
|
501
501
|
args = []
|
|
502
|
-
if
|
|
503
|
-
|
|
504
|
-
|
|
502
|
+
if abort
|
|
503
|
+
# ABORT is mutually exclusive: cancels an ongoing failover and
|
|
504
|
+
# ignores any other arguments (matches the core GLIDE clients).
|
|
505
|
+
args << "ABORT"
|
|
506
|
+
else
|
|
507
|
+
if to
|
|
508
|
+
host, port = to.split
|
|
509
|
+
args << "TO" << host << port
|
|
510
|
+
# FORCE is only valid in combination with TO.
|
|
511
|
+
args << "FORCE" if force
|
|
512
|
+
end
|
|
513
|
+
args << "TIMEOUT" << timeout.to_s if timeout
|
|
505
514
|
end
|
|
506
|
-
args << "FORCE" if force
|
|
507
|
-
args << "ABORT" if abort
|
|
508
|
-
args << "TIMEOUT" << timeout.to_s if timeout
|
|
509
515
|
send_command(RequestType::FAIL_OVER, args)
|
|
510
516
|
end
|
|
511
517
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/valkey/opentelemetry.rb
CHANGED
|
@@ -44,6 +44,7 @@ class Valkey
|
|
|
44
44
|
module OpenTelemetry
|
|
45
45
|
@initialized = false
|
|
46
46
|
@config = nil
|
|
47
|
+
@parent_span_context_provider = nil
|
|
47
48
|
|
|
48
49
|
class << self
|
|
49
50
|
# Initialize OpenTelemetry in the Valkey GLIDE core.
|
|
@@ -67,12 +68,16 @@ class Valkey
|
|
|
67
68
|
# @param flush_interval_ms [Integer, nil] Flush interval in milliseconds (default: 5000)
|
|
68
69
|
# Must be a positive integer
|
|
69
70
|
#
|
|
71
|
+
# @param parent_span_context_provider [Proc, nil] Optional callable returning a Hash describing
|
|
72
|
+
# the current application span context (see {set_parent_span_context_provider}). Equivalent to
|
|
73
|
+
# calling {set_parent_span_context_provider} separately; provided here for convenience.
|
|
74
|
+
#
|
|
70
75
|
# @raise [ArgumentError] if neither traces nor metrics is provided
|
|
71
76
|
# @raise [ArgumentError] if sample_percentage is not between 0-100
|
|
72
77
|
# @raise [RuntimeError] if initialization fails
|
|
73
78
|
#
|
|
74
79
|
# @return [void]
|
|
75
|
-
def init(traces: nil, metrics: nil, flush_interval_ms: nil)
|
|
80
|
+
def init(traces: nil, metrics: nil, flush_interval_ms: nil, parent_span_context_provider: nil)
|
|
76
81
|
if @initialized
|
|
77
82
|
warn "Valkey::OpenTelemetry already initialized - ignoring new configuration"
|
|
78
83
|
return
|
|
@@ -106,6 +111,7 @@ class Valkey
|
|
|
106
111
|
|
|
107
112
|
@initialized = true
|
|
108
113
|
@config = { traces: traces, metrics: metrics, flush_interval_ms: flush_interval_ms }
|
|
114
|
+
set_parent_span_context_provider(parent_span_context_provider) if parent_span_context_provider
|
|
109
115
|
|
|
110
116
|
puts "✅ Valkey OpenTelemetry initialized successfully"
|
|
111
117
|
puts " Traces: #{traces ? traces[:endpoint] : 'disabled'}"
|
|
@@ -135,16 +141,88 @@ class Valkey
|
|
|
135
141
|
# @return [Hash, nil] the configuration hash or nil if not initialized
|
|
136
142
|
attr_reader :config
|
|
137
143
|
|
|
144
|
+
# Register a callable that returns the current application span context, so that
|
|
145
|
+
# spans created for Valkey commands become children of it instead of independent
|
|
146
|
+
# root spans. This is how distributed tracing context (e.g. from the Rails request
|
|
147
|
+
# span) is propagated into the native OpenTelemetry spans created for each command.
|
|
148
|
+
#
|
|
149
|
+
# @param callable [Proc, nil] Called with no arguments before each command. Must return
|
|
150
|
+
# either nil (no active context - the command gets an independent span) or a Hash with:
|
|
151
|
+
# - :trace_id [String] 32-character lowercase hex trace ID
|
|
152
|
+
# - :span_id [String] 16-character lowercase hex span ID
|
|
153
|
+
# - :trace_flags [Integer] 0-255
|
|
154
|
+
# - :tracestate [String, nil] W3C tracestate header, optional
|
|
155
|
+
# Pass nil (and no block) to clear a previously registered provider.
|
|
156
|
+
#
|
|
157
|
+
# @example
|
|
158
|
+
# Valkey::OpenTelemetry.set_parent_span_context_provider do
|
|
159
|
+
# span = ::OpenTelemetry::Trace.current_span
|
|
160
|
+
# next nil unless span.context.valid?
|
|
161
|
+
#
|
|
162
|
+
# {
|
|
163
|
+
# trace_id: span.context.hex_trace_id,
|
|
164
|
+
# span_id: span.context.hex_span_id,
|
|
165
|
+
# trace_flags: span.context.trace_flags.sampled? ? 1 : 0,
|
|
166
|
+
# tracestate: span.context.tracestate.to_s
|
|
167
|
+
# }
|
|
168
|
+
# end
|
|
169
|
+
#
|
|
170
|
+
# @return [void]
|
|
171
|
+
def set_parent_span_context_provider(callable = nil, &block)
|
|
172
|
+
@parent_span_context_provider = block || callable
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Invoke the registered parent-span-context provider (if any) and return a validated
|
|
176
|
+
# context Hash, or nil if no provider is registered, the provider returned nil, the
|
|
177
|
+
# provider raised, or the returned context failed validation.
|
|
178
|
+
#
|
|
179
|
+
# @return [Hash, nil]
|
|
180
|
+
def parent_span_context
|
|
181
|
+
return nil unless @parent_span_context_provider
|
|
182
|
+
|
|
183
|
+
ctx = @parent_span_context_provider.call
|
|
184
|
+
return nil if ctx.nil?
|
|
185
|
+
|
|
186
|
+
validate_parent_span_context!(ctx)
|
|
187
|
+
ctx
|
|
188
|
+
rescue StandardError => e
|
|
189
|
+
warn "Valkey::OpenTelemetry parent_span_context_provider failed: #{e.message}"
|
|
190
|
+
nil
|
|
191
|
+
end
|
|
192
|
+
|
|
138
193
|
# Reset initialization state (for testing only).
|
|
139
194
|
#
|
|
140
195
|
# @api private
|
|
141
196
|
def reset!
|
|
142
197
|
@initialized = false
|
|
143
198
|
@config = nil
|
|
199
|
+
@parent_span_context_provider = nil
|
|
144
200
|
end
|
|
145
201
|
|
|
146
202
|
private
|
|
147
203
|
|
|
204
|
+
def validate_parent_span_context!(ctx)
|
|
205
|
+
raise ArgumentError, "parent span context must be a Hash, got: #{ctx.class}" unless ctx.is_a?(Hash)
|
|
206
|
+
|
|
207
|
+
unless ctx[:trace_id].is_a?(String) && ctx[:trace_id].match?(/\A[0-9a-f]{32}\z/)
|
|
208
|
+
raise ArgumentError, "trace_id must be a 32-character lowercase hex string, got: #{ctx[:trace_id].inspect}"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
unless ctx[:span_id].is_a?(String) && ctx[:span_id].match?(/\A[0-9a-f]{16}\z/)
|
|
212
|
+
raise ArgumentError, "span_id must be a 16-character lowercase hex string, got: #{ctx[:span_id].inspect}"
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
trace_flags = ctx[:trace_flags]
|
|
216
|
+
unless trace_flags.is_a?(Integer) && trace_flags >= 0 && trace_flags <= 255
|
|
217
|
+
raise ArgumentError, "trace_flags must be an integer between 0 and 255, got: #{trace_flags.inspect}"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
tracestate = ctx[:tracestate]
|
|
221
|
+
return if tracestate.nil? || tracestate.is_a?(String)
|
|
222
|
+
|
|
223
|
+
raise ArgumentError, "tracestate must be a String or nil, got: #{tracestate.class}"
|
|
224
|
+
end
|
|
225
|
+
|
|
148
226
|
def build_config(traces, metrics, flush_interval_ms)
|
|
149
227
|
config_struct = Bindings::OpenTelemetryConfig.new
|
|
150
228
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Valkey
|
|
4
|
+
#
|
|
5
|
+
# this module defines constants for the `read_from:` connection option.
|
|
6
|
+
# Each constant is the canonical GLIDE string for that read-routing strategy,
|
|
7
|
+
# matching the RequestType/ResponseType constant modules' convention in this
|
|
8
|
+
# gem. Ruby symbols (`:prefer_replica`) and the exact-match strings
|
|
9
|
+
# (`"PreferReplica"`) are still accepted directly by `Valkey.new` -- these
|
|
10
|
+
# constants are purely an additional, IDE-completion-friendly way to write
|
|
11
|
+
# the same values, not a new validation mechanism.
|
|
12
|
+
#
|
|
13
|
+
module ReadFrom
|
|
14
|
+
PRIMARY = "Primary"
|
|
15
|
+
PREFER_REPLICA = "PreferReplica"
|
|
16
|
+
AZ_AFFINITY = "AZAffinity"
|
|
17
|
+
AZ_AFFINITY_REPLICAS_AND_PRIMARY = "AZAffinityReplicasAndPrimary"
|
|
18
|
+
|
|
19
|
+
# "LowestLatency" is a valid GLIDE value but not yet usable via the vendored
|
|
20
|
+
# native library (panics in ConnectionRequest::from, see types.rs) -- not
|
|
21
|
+
# defined as a constant here since it can't work today, though passing the
|
|
22
|
+
# raw string through directly is still forwarded to the core unchanged.
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/valkey/version.rb
CHANGED