Module: OpenTelemetry::Exporter::OTLP::Metrics::Util

Included in:
MetricsExporter
Defined in:
lib/opentelemetry/exporter/otlp/metrics/util.rb

Overview

Util module provide essential functionality for exporter

Constant Summary collapse

KEEP_ALIVE_TIMEOUT =

rubocop:disable Metrics/ModuleLength

30
RETRY_COUNT =
5
ERROR_MESSAGE_INVALID_HEADERS =
'headers must be a String with comma-separated URL Encoded UTF-8 k=v pairs or a Hash'
DEFAULT_USER_AGENT =
"OTel-OTLP-MetricsExporter-Ruby/#{OpenTelemetry::Exporter::OTLP::Metrics::VERSION} Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM}; #{RUBY_ENGINE}/#{RUBY_ENGINE_VERSION})".freeze

Instance Method Summary collapse

Instance Method Details

#around_requestObject



27
28
29
# File 'lib/opentelemetry/exporter/otlp/metrics/util.rb', line 27

def around_request
  OpenTelemetry::Common::Utilities.untraced { yield } # rubocop:disable Style/ExplicitBlockArgument
end

#as_otlp_any_value(value) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/opentelemetry/exporter/otlp/metrics/util.rb', line 39

def as_otlp_any_value(value)
  result = Opentelemetry::Proto::Common::V1::AnyValue.new
  case value
  when String
    result.string_value = value
  when Integer
    result.int_value = value
  when Float
    result.double_value = value
  when true, false
    result.bool_value = value
  when Array
    values = value.map { |element| as_otlp_any_value(element) }
    result.array_value = Opentelemetry::Proto::Common::V1::ArrayValue.new(values: values)
  end
  result
end

#as_otlp_key_value(key, value) ⇒ Object



31
32
33
34
35
36
37
# File 'lib/opentelemetry/exporter/otlp/metrics/util.rb', line 31

def as_otlp_key_value(key, value)
  Opentelemetry::Proto::Common::V1::KeyValue.new(key: key, value: as_otlp_any_value(value))
rescue Encoding::UndefinedConversionError => e
  encoded_value = value.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
  OpenTelemetry.handle_error(exception: e, message: "encoding error for key #{key} and value #{encoded_value}")
  Opentelemetry::Proto::Common::V1::KeyValue.new(key: key, value: as_otlp_any_value('Encoding Error'))
end

#backoff?(retry_count:, reason:, retry_after: nil) ⇒ Boolean

rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

Returns:

  • (Boolean)


100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/opentelemetry/exporter/otlp/metrics/util.rb', line 100

def backoff?(retry_count:, reason:, retry_after: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
  return false if retry_count > RETRY_COUNT

  sleep_interval = nil
  unless retry_after.nil?
    sleep_interval =
      begin
        Integer(retry_after)
      rescue ArgumentError
        nil
      end
    sleep_interval ||=
      begin
        Time.httpdate(retry_after) - Time.now
      rescue # rubocop:disable Style/RescueStandardError
        nil
      end
    sleep_interval = nil unless sleep_interval&.positive?
  end
  sleep_interval ||= rand(2**retry_count)

  sleep(sleep_interval)
  true
end

#handle_redirect(location) ⇒ Object



136
# File 'lib/opentelemetry/exporter/otlp/metrics/util.rb', line 136

def handle_redirect(location); end

#http_connection(uri, ssl_verify_mode, certificate_file) ⇒ Object



18
19
20
21
22
23
24
25
# File 'lib/opentelemetry/exporter/otlp/metrics/util.rb', line 18

def http_connection(uri, ssl_verify_mode, certificate_file)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = uri.scheme == 'https'
  http.verify_mode = ssl_verify_mode
  http.ca_file = certificate_file unless certificate_file.nil?
  http.keep_alive_timeout = KEEP_ALIVE_TIMEOUT
  http
end

#log_status(body) ⇒ Object



125
126
127
128
129
130
131
132
133
134
# File 'lib/opentelemetry/exporter/otlp/metrics/util.rb', line 125

def log_status(body)
  status = Google::Rpc::Status.decode(body)
  details = status.details.map do |detail|
    klass_or_nil = ::Google::Protobuf::DescriptorPool.generated_pool.lookup(detail.type_name).msgclass
    detail.unpack(klass_or_nil) if klass_or_nil
  end.compact
  OpenTelemetry.handle_error(message: "OTLP metrics_exporter received rpc.Status{message=#{status.message}, details=#{details}}")
rescue StandardError => e
  OpenTelemetry.handle_error(exception: e, message: 'unexpected error decoding rpc.Status in OTLP::MetricsExporter#log_status')
end

#measure_request_durationObject



70
71
72
73
74
75
76
77
78
# File 'lib/opentelemetry/exporter/otlp/metrics/util.rb', line 70

def measure_request_duration
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  begin
    yield
  ensure
    stop = Process.clock_gettime(Process::CLOCK_MONOTONIC)
    1000.0 * (stop - start)
  end
end

#parse_headers(raw) ⇒ Object

Raises:

  • (ArgumentError)


80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/opentelemetry/exporter/otlp/metrics/util.rb', line 80

def parse_headers(raw)
  entries = raw.split(',')
  raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS if entries.empty?

  entries.each_with_object({}) do |entry, headers|
    k, v = entry.split('=', 2).map(&CGI.method(:unescape))
    begin
      k = k.to_s.strip
      v = v.to_s.strip
    rescue Encoding::CompatibilityError
      raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS
    rescue ArgumentError => e
      raise e, ERROR_MESSAGE_INVALID_HEADERS
    end
    raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS if k.empty? || v.empty?

    headers[k] = v
  end
end

#prepare_headers(config_headers) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/opentelemetry/exporter/otlp/metrics/util.rb', line 57

def prepare_headers(config_headers)
  headers = case config_headers
            when String then parse_headers(config_headers)
            when Hash then config_headers.dup
            else
              raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS
            end

  headers['User-Agent'] = "#{headers.fetch('User-Agent', '')} #{DEFAULT_USER_AGENT}".strip

  headers
end