Class: Datadog::OpenFeature::Hooks::SpanEnrichmentHook::SpanEnrichmentState

Inherits:
Object
  • Object
show all
Defined in:
lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state.rb

Overview

Per-root-span state. Enforces the frozen limits, dedupes serial ids structurally via a Set, and renders the three ffe_* tag shapes.

Not internally synchronized: every method is only ever called while the owning SpanEnrichmentHook's mutex is held (capture, encode, cleanup), so the state stays a plain object and the lock provides the consistent snapshot at encode time.

Instance Method Summary collapse

Constructor Details

#initializeSpanEnrichmentState

Returns a new instance of SpanEnrichmentState.



20
21
22
23
24
# File 'lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state.rb', line 20

def initialize
  @serial_ids = Set.new
  @subjects = {} # sha256hex => Set<int>
  @defaults = {} # flag_key => String
end

Instance Method Details

#add_default(flag_key, value) ⇒ Object

value is the flag's resolved runtime-default — any OpenFeature value type (String, boolean, number, or a structured Hash/Array). Non-strings are JSON-encoded (not to_s) so a structured default is captured faithfully rather than as Ruby's inspect form (e.g. {"a"=>1}, which is not valid JSON). The result is a bounded diagnostic PREVIEW, not a round-trippable value: it is truncated to the frozen 64-char cap, so a long JSON string is intentionally left as a non-parseable prefix. The backend treats this field as a display string, so a clipped value is acceptable.



62
63
64
65
66
67
68
69
70
# File 'lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state.rb', line 62

def add_default(flag_key, value)
  return if @defaults.key?(flag_key) # first-wins
  return if @defaults.size >= MAX_DEFAULTS

  value_str = value.is_a?(String) ? value : JSON.generate(value)
  # Slicing is by codepoint, so a multibyte UTF-8 character is never
  # split; `|| value_str` keeps the value non-nil for the zero-start slice.
  @defaults[flag_key] = value_str[0, MAX_DEFAULT_VALUE_LENGTH] || value_str
end

#add_serial_id(serial_id) ⇒ Object



26
27
28
29
30
# File 'lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state.rb', line 26

def add_serial_id(serial_id)
  return if @serial_ids.size >= MAX_SERIAL_IDS

  @serial_ids.add(serial_id)
end

#add_subject(targeting_key, serial_id) ⇒ Object

Subject serial ids are NOT required to be a subset of the flag set: the frozen cross-SDK wire contract does not mandate subjects ⊆ flags, so a subject may reference a serial id that the independent 200-flag cap dropped from ffe_flags_enc. This mirrors the reference implementation and every other SDK; the caps (200 flags, 10 subjects, 20 experiments) are applied independently on purpose.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state.rb', line 38

def add_subject(targeting_key, serial_id)
  hashed = Codec.hash_targeting_key(targeting_key)
  existing = @subjects[hashed]

  if existing
    return if existing.size >= MAX_EXPERIMENTS_PER_SUBJECT

    existing.add(serial_id)
  elsif @subjects.size >= MAX_SUBJECTS
    nil
  else
    @subjects[hashed] = Set[serial_id]
  end
end

#has_data?Boolean

Subjects are intentionally not checked: a subject is only ever added alongside a serial id, so serial ids cover that case.

Returns:

  • (Boolean)


74
75
76
# File 'lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state.rb', line 74

def has_data?
  @serial_ids.any? || @defaults.any?
end

#to_span_tagsObject



78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state.rb', line 78

def to_span_tags
  tags = {}
  tags[TAG_FLAGS_ENC] = Codec.encode_delta_varint(@serial_ids) if @serial_ids.any?

  if @subjects.any?
    encoded_subjects = {}
    @subjects.each { |hashed, ids| encoded_subjects[hashed] = Codec.encode_delta_varint(ids) }
    tags[TAG_SUBJECTS_ENC] = JSON.generate(encoded_subjects)
  end

  tags[TAG_RUNTIME_DEFAULTS] = JSON.generate(@defaults) if @defaults.any?

  tags
end