Module: Valkey::OpenTelemetry

Defined in:
lib/valkey/opentelemetry.rb

Overview

OpenTelemetry integration for Valkey GLIDE Ruby client.

This module provides integration with the OpenTelemetry implementation built into the Valkey GLIDE core (Rust FFI layer). Unlike typical Ruby OpenTelemetry instrumentation, this directly configures the native OpenTelemetry exporter in the Rust layer.

Examples:

Initialize with HTTP collector

Valkey::OpenTelemetry.init(
  traces: {
    endpoint: "http://localhost:4318/v1/traces",
    sample_percentage: 10
  },
  metrics: {
    endpoint: "http://localhost:4318/v1/metrics"
  },
  flush_interval_ms: 5000
)

Initialize with gRPC collector

Valkey::OpenTelemetry.init(
  traces: {
    endpoint: "grpc://localhost:4317",
    sample_percentage: 1
  },
  metrics: {
    endpoint: "grpc://localhost:4317"
  }
)

Initialize with file exporter (for testing)

Valkey::OpenTelemetry.init(
  traces: {
    endpoint: "file:///tmp/valkey_traces.json",
    sample_percentage: 100
  },
  metrics: {
    endpoint: "file:///tmp/valkey_metrics.json"
  }
)

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.configHash? (readonly)

Get the current OpenTelemetry configuration.

Returns:

  • (Hash, nil)

    the configuration hash or nil if not initialized



144
145
146
# File 'lib/valkey/opentelemetry.rb', line 144

def config
  @config
end

Class Method Details

.init(traces: nil, metrics: nil, flush_interval_ms: nil, parent_span_context_provider: nil) ⇒ void

This method returns an undefined value.

Initialize OpenTelemetry in the Valkey GLIDE core.

This method can only be called once per process. Subsequent calls will be ignored with a warning.

Parameters:

  • traces (Hash, nil) (defaults to: nil)

    Traces configuration

  • metrics (Hash, nil) (defaults to: nil)

    Metrics configuration

  • flush_interval_ms (Integer, nil) (defaults to: nil)

    Flush interval in milliseconds (default: 5000) Must be a positive integer

  • parent_span_context_provider (Proc, nil) (defaults to: nil)

    Optional callable returning a Hash describing the current application span context (see set_parent_span_context_provider). Equivalent to calling set_parent_span_context_provider separately; provided here for convenience.

Options Hash (traces:):

  • :endpoint (String)

    The endpoint URL (required) Supported formats:

  • :sample_percentage (Integer)

    Sample percentage 0-100 (default: 1) Keep low (1-5%) in production for performance

Options Hash (metrics:):

  • :endpoint (String)

    The endpoint URL (required) Same format as traces endpoint

Raises:

  • (ArgumentError)

    if neither traces nor metrics is provided

  • (ArgumentError)

    if sample_percentage is not between 0-100

  • (RuntimeError)

    if initialization fails



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/valkey/opentelemetry.rb', line 80

def init(traces: nil, metrics: nil, flush_interval_ms: nil, parent_span_context_provider: nil)
  if @initialized
    warn "Valkey::OpenTelemetry already initialized - ignoring new configuration"
    return
  end

  # Validate input
  raise ArgumentError, "At least one of traces or metrics must be provided" if traces.nil? && metrics.nil?

  if traces && traces[:sample_percentage]
    sample = traces[:sample_percentage]
    unless sample.is_a?(Integer) && sample >= 0 && sample <= 100
      raise ArgumentError, "sample_percentage must be an integer between 0 and 100, got: #{sample}"
    end
  end

  if flush_interval_ms && (!flush_interval_ms.is_a?(Integer) || flush_interval_ms <= 0)
    raise ArgumentError, "flush_interval_ms must be a positive integer, got: #{flush_interval_ms}"
  end

  # Build the configuration
  # keep_alive needs to be referenced so not to be GC'd before init_open_telemetry
  # TODO: Refactor per https://github.com/valkey-io/valkey-glide-ruby/issues/179
  config, keep_alive = build_config(traces, metrics, flush_interval_ms)

  error_ptr = Bindings.init_open_telemetry(config)
  keep_alive.clear # Needed to avoid linter warning.

  unless error_ptr.null?
    error_msg = error_ptr.read_string
    Bindings.free_c_string(error_ptr)
    raise "Failed to initialize OpenTelemetry: #{error_msg}"
  end

  @initialized = true
  @config = { traces: traces, metrics: metrics, flush_interval_ms: flush_interval_ms }
  set_parent_span_context_provider(parent_span_context_provider) if parent_span_context_provider

  puts "✅ Valkey OpenTelemetry initialized successfully"
  puts "   Traces: #{traces ? traces[:endpoint] : 'disabled'}"
  puts "   Metrics: #{metrics ? metrics[:endpoint] : 'disabled'}"
end

.initialized?Boolean

Check if OpenTelemetry has been initialized.

Returns:

  • (Boolean)

    true if initialized



126
127
128
# File 'lib/valkey/opentelemetry.rb', line 126

def initialized?
  @initialized
end

.parent_span_contextHash?

Invoke the registered parent-span-context provider (if any) and return a validated context Hash, or nil if no provider is registered, the provider returned nil, the provider raised, or the returned context failed validation.

Returns:

  • (Hash, nil)


182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/valkey/opentelemetry.rb', line 182

def parent_span_context
  return nil unless @parent_span_context_provider

  ctx = @parent_span_context_provider.call
  return nil if ctx.nil?

  validate_parent_span_context!(ctx)
  ctx
rescue StandardError => e
  warn "Valkey::OpenTelemetry parent_span_context_provider failed: #{e.message}"
  nil
end

.reset!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Reset initialization state (for testing only).



198
199
200
201
202
# File 'lib/valkey/opentelemetry.rb', line 198

def reset!
  @initialized = false
  @config = nil
  @parent_span_context_provider = nil
end

.set_parent_span_context_provider(callable = nil, &block) ⇒ void

This method returns an undefined value.

Register a callable that returns the current application span context, so that spans created for Valkey commands become children of it instead of independent root spans. This is how distributed tracing context (e.g. from the Rails request span) is propagated into the native OpenTelemetry spans created for each command.

Examples:

Valkey::OpenTelemetry.set_parent_span_context_provider do
  span = ::OpenTelemetry::Trace.current_span
  next nil unless span.context.valid?

  {
    trace_id: span.context.hex_trace_id,
    span_id: span.context.hex_span_id,
    trace_flags: span.context.trace_flags.sampled? ? 1 : 0,
    tracestate: span.context.tracestate.to_s
  }
end

Parameters:

  • callable (Proc, nil) (defaults to: nil)

    Called with no arguments before each command. Must return either nil (no active context - the command gets an independent span) or a Hash with:

    • :trace_id [String] 32-character lowercase hex trace ID
    • :span_id [String] 16-character lowercase hex span ID
    • :trace_flags [Integer] 0-255
    • :tracestate [String, nil] W3C tracestate header, optional Pass nil (and no block) to clear a previously registered provider.


173
174
175
# File 'lib/valkey/opentelemetry.rb', line 173

def set_parent_span_context_provider(callable = nil, &block)
  @parent_span_context_provider = block || callable
end

.should_sample?Boolean

Determine if the current request should be sampled based on the configured sample percentage.

Returns:

  • (Boolean)

    true if the request should be sampled



133
134
135
136
137
138
139
# File 'lib/valkey/opentelemetry.rb', line 133

def should_sample?
  return false unless @initialized
  return false unless @config&.dig(:traces)

  sample_percentage = @config.dig(:traces, :sample_percentage) || 1
  rand(100) < sample_percentage
end