lib/elastic_apm/trace_context/tracestate.rb in elastic-apm-3.10.1 vs lib/elastic_apm/trace_context/tracestate.rb in elastic-apm-3.11.0

- old
+ new

@@ -15,29 +15,132 @@ # specific language governing permissions and limitations # under the License. # frozen_string_literal: true +require 'elastic_apm/util/precision_validator' + module ElasticAPM class TraceContext # @api private class Tracestate - def initialize(values = []) - @values = values + # @api private + class Entry + def initialize(key, value) + @key = key + @value = value + end + + attr_reader :key, :value + + def to_s + "#{key}=#{value}" + end end - attr_accessor :values + class EsEntry + ASSIGN = ':' + SPLIT = ';' + SHORT_TO_LONG = { 's' => 'sample_rate' } + LONG_TO_SHORT = { 'sample_rate' => 's' } + + def initialize(values = nil) + parse(values) + end + + attr_reader :sample_rate + + def key + 'es' + end + + def value + LONG_TO_SHORT.map do |l, s| + "#{s}#{ASSIGN}#{send(l)}" + end.join(SPLIT) + end + + def empty? + !sample_rate + end + + def sample_rate=(val) + @sample_rate = Util::PrecisionValidator.validate( + val, precision: 4, minimum: 0.0001 + ) + end + + def to_s + return nil if empty? + + "es=#{value}" + end + + private + + def parse(values) + return unless values + + values.split(SPLIT).map do |kv| + k, v = kv.split(ASSIGN) + next unless SHORT_TO_LONG.keys.include?(k) + send("#{SHORT_TO_LONG[k]}=", v) + end + end + end + + extend Forwardable + + def initialize(entries: {}, sample_rate: nil) + @entries = entries + + self.sample_rate = sample_rate if sample_rate + end + + attr_accessor :entries + + def_delegators :es_entry, :sample_rate, :sample_rate= + def self.parse(header) - # HTTP allows multiple headers with the same name, eg. multiple - # Set-Cookie headers per response. - # Rack handles this by joining the headers under the same key, separated - # by newlines, see https://www.rubydoc.info/github/rack/rack/file/SPEC - new(String(header).split("\n")) + entries = + split_by_nl_and_comma(header) + .each_with_object({}) do |entry, hsh| + k, v = entry.split('=') + + hsh[k] = + case k + when 'es' then EsEntry.new(v) + else Entry.new(k, v) + end + end + + new(entries: entries) end def to_header - values.join(',') + return "" unless entries.any? + + entries.values.map(&:to_s).join(',') + end + + private + + def es_entry + # lazy generate this so we only add it if necessary + entries['es'] ||= EsEntry.new + end + + class << self + private + + def split_by_nl_and_comma(str) + # HTTP allows multiple headers with the same name, eg. multiple + # Set-Cookie headers per response. + # Rack handles this by joining the headers under the same key, separated + # by newlines, see https://www.rubydoc.info/github/rack/rack/file/SPEC + String(str).split("\n").map { |s| s.split(',') }.flatten + end end end end end