Module: Bitfab::Otel::Encoder

Defined in:
lib/bitfab/otel.rb

Overview

Turns finished carrier spans into the OTLP/JSON shapes Bitfab ingests.

Class Method Summary collapse

Class Method Details

.encode_request(envelope, spans) ⇒ Object



669
670
671
# File 'lib/bitfab/otel.rb', line 669

def encode_request(envelope, spans)
  envelope.head + spans.map(&:encoded).join(",") + envelope.tail
end

.encode_span(span) ⇒ Object



641
642
643
644
# File 'lib/bitfab/otel.rb', line 641

def encode_span(span)
  encoded = JSON.generate(span_to_otlp(span))
  EncodedSpan.new(encoded, encoded.bytesize)
end

.error?(payload) ⇒ Boolean

Returns:

  • (Boolean)


720
721
722
723
724
725
726
# File 'lib/bitfab/otel.rb', line 720

def error?(payload)
  raw_span = payload["rawSpan"]
  return true if raw_span.is_a?(Hash) && !raw_span.dig("span_data", "error").nil?

  errors = payload["errors"]
  errors.is_a?(Array) && !errors.empty?
end

.request_envelope(first) ⇒ Object



660
661
662
663
664
665
666
667
# File 'lib/bitfab/otel.rb', line 660

def request_envelope(first)
  scope = first.instrumentation_scope
  resource = JSON.generate({"attributes" => attributes(first.resource.attribute_enumerator.to_h)})
  scope_json = JSON.generate({"name" => scope.name, "version" => scope.version || ""})
  head = %({"resourceSpans":[{"resource":#{resource},"scopeSpans":[{"scope":#{scope_json},"spans":[)
  tail = "]}]}]}"
  RequestEnvelope.new(head, tail, (head + tail).bytesize)
end

.span_name(operation, payload) ⇒ Object



694
695
696
697
698
699
700
701
702
703
# File 'lib/bitfab/otel.rb', line 694

def span_name(operation, payload)
  raw_span = payload["rawSpan"]
  if operation == "external_span" && raw_span.is_a?(Hash)
    name = raw_span.dig("span_data", "name")
    return name if name.is_a?(String)
  end
  return payload["traceFunctionKey"] if payload["traceFunctionKey"].is_a?(String)

  "bitfab.#{operation}"
end

.span_to_otlp(span) ⇒ Object



673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
# File 'lib/bitfab/otel.rb', line 673

def span_to_otlp(span)
  result = {
    "traceId" => span.hex_trace_id,
    "spanId" => span.hex_span_id,
    "name" => span.name,
    "kind" => SPAN_KINDS.fetch(span.kind, 1),
    "startTimeUnixNano" => (span.start_timestamp || 0).to_s,
    "endTimeUnixNano" => (span.end_timestamp || 0).to_s,
    "attributes" => attributes(span.attributes),
    "droppedAttributesCount" => dropped_count(span.total_recorded_attributes, span.attributes),
    "droppedEventsCount" => dropped_count(span.total_recorded_events, span.events),
    "droppedLinksCount" => dropped_count(span.total_recorded_links, span.links),
    "status" => status(span.status),
    "flags" => span.trace_flags.sampled? ? 1 : 0
  }
  result["parentSpanId"] = span.hex_parent_span_id unless span.parent_span_id == INVALID_SPAN_ID
  tracestate = span.tracestate&.to_s
  result["traceState"] = tracestate unless tracestate.nil? || tracestate.empty?
  result
end

.timestamp(payload, field) ⇒ Object



705
706
707
708
709
710
711
712
713
714
715
716
717
718
# File 'lib/bitfab/otel.rb', line 705

def timestamp(payload, field)
  raw = payload.dig("rawSpan", field) if payload["rawSpan"].is_a?(Hash)
  if raw.nil?
    raw_trace = payload["externalTrace"] || payload["rawTrace"]
    raw = raw_trace[field] if raw_trace.is_a?(Hash)
  end
  return Time.now unless raw.is_a?(String)

  begin
    Time.iso8601(raw)
  rescue ArgumentError
    Time.now
  end
end

.trim_span(span) ⇒ Object



646
647
648
649
650
651
652
653
654
655
656
657
658
# File 'lib/bitfab/otel.rb', line 646

def trim_span(span)
  carrier = JSON.parse(span.encoded)
  attribute = carrier.fetch("attributes").find { |entry| entry["key"] == PAYLOAD_ATTRIBUTE }
  return nil if attribute.nil?

  value = attribute.fetch("value")
  payload = JSON.parse(value.fetch("stringValue"))
  value["stringValue"] = Serialize.safe_generate(payload)
  encoded = JSON.generate(carrier)
  EncodedSpan.new(encoded, encoded.bytesize)
rescue KeyError, JSON::ParserError, TypeError
  nil
end