class Uptrace::Trace::Exporter

Exporter is a span exporter for OpenTelemetry.

Constants

FAILURE
SUCCESS
TIMEOUT

Public Class Methods

new(dsn) click to toggle source

@param [Config] cfg

# File lib/uptrace/trace/exporter.rb, line 23
def initialize(dsn)
  @dsn = dsn
  @endpoint = "/api/v1/tracing/#{@dsn.project_id}/spans"

  @http = Net::HTTP.new(@dsn.host, 443)
  @http.use_ssl = true
  @http.open_timeout = 5
  @http.read_timeout = 5
  @http.keep_alive_timeout = 30
end

Public Instance Methods

export(spans, timeout: nil) click to toggle source

Called to export sampled {OpenTelemetry::SDK::Trace::SpanData} structs.

@param [Enumerable<OpenTelemetry::SDK::Trace::SpanData>] spans the

list of recorded {OpenTelemetry::SDK::Trace::SpanData} structs to be
exported.

@param [optional Numeric] timeout An optional timeout in seconds. @return [Integer] the result of the export.

# File lib/uptrace/trace/exporter.rb, line 41
def export(spans, timeout: nil)
  return SUCCESS if @disabled
  return FAILURE if @shutdown

  out = []

  spans.each do |span|
    out.push(uptrace_span(span))
  end

  send({ spans: out }, timeout: timeout)
end
force_flush(timeout: nil) click to toggle source

Called when {OpenTelemetry::SDK::Trace::TracerProvider#force_flush} is called, if this exporter is registered to a {OpenTelemetry::SDK::Trace::TracerProvider} object.

@param [optional Numeric] timeout An optional timeout in seconds.

# File lib/uptrace/trace/exporter.rb, line 59
def force_flush(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
  SUCCESS
end
shutdown(timeout: nil) click to toggle source

Called when {OpenTelemetry::SDK::Trace::Tracer#shutdown} is called, if this exporter is registered to a {OpenTelemetry::SDK::Trace::Tracer} object.

@param [optional Numeric] timeout An optional timeout in seconds.

# File lib/uptrace/trace/exporter.rb, line 68
def shutdown(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
  @shutdown = true
  @http.finish if @http.started?
  SUCCESS
end

Private Instance Methods

as_unix_nano(timestamp) click to toggle source

@param [Integer] timestamp @return [Integer]

# File lib/uptrace/trace/exporter.rb, line 174
def as_unix_nano(timestamp)
  (timestamp.to_r * 1_000_000_000).to_i
end
build_request(data) click to toggle source

@param [Hash] data @return [Net::HTTP::Post]

# File lib/uptrace/trace/exporter.rb, line 139
def build_request(data)
  data = MessagePack.pack(data)
  data = Zstd.compress(data, 3)

  req = Net::HTTP::Post.new(@endpoint)
  req.add_field('Authorization', "Bearer #{@dsn.token}")
  req.add_field('Content-Type', 'application/msgpack')
  req.add_field('Content-Encoding', 'zstd')
  req.add_field('Connection', 'keep-alive')
  req.body = data

  req
end
kind_as_str(kind) click to toggle source

@param [SpanKind] kind @return [String]

# File lib/uptrace/trace/exporter.rb, line 155
def kind_as_str(kind)
  case kind
  when OpenTelemetry::Trace::SpanKind::SERVER
    'server'
  when OpenTelemetry::Trace::SpanKind::CLIENT
    'client'
  when OpenTelemetry::Trace::SpanKind::PRODUCER
    'producer'
  when OpenTelemetry::Trace::SpanKind::CONSUMER
    'consumer'
  else
    'internal'
  end
end
send(data, timeout: nil) click to toggle source
# File lib/uptrace/trace/exporter.rb, line 109
def send(data, timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
  req = build_request(data)

  begin
    resp = @http.request(req)
  rescue Net::OpenTimeout, Net::ReadTimeout
    return FAILURE
  end

  case resp
  when Net::HTTPOK
    resp.body # Read and discard body
    SUCCESS
  when Net::HTTPBadRequest
    data = JSON.parse(resp.body)
    Uptrace.logger.error("status=#{data['status']}: #{data['message']}")
    FAILURE
  when Net::HTTPInternalServerError
    resp.body
    FAILURE
  else
    @http.finish
    FAILURE
  end
end
status_code_as_str(code) click to toggle source

@param [Integer] code @return [String]

# File lib/uptrace/trace/exporter.rb, line 182
def status_code_as_str(code)
  case code
  when OpenTelemetry::Trace::Status::OK
    'ok'
  when OpenTelemetry::Trace::Status::ERROR
    'error'
  else
    'unset'
  end
end
uptrace_events(events) click to toggle source
# File lib/uptrace/trace/exporter.rb, line 203
def uptrace_events(events)
  out = []
  events.each do |event|
    out.push(
      {
        name: event.name,
        attrs: event.attributes,
        time: as_unix_nano(event.timestamp)
      }
    )
  end
  out
end
uptrace_resource(resource) click to toggle source

@param [OpenTelemetry::SDK::Resources::Resource] resource @return [Hash]

# File lib/uptrace/trace/exporter.rb, line 197
def uptrace_resource(resource)
  out = {}
  resource.attribute_enumerator.map { |key, value| out[key] = value }
  out
end
uptrace_span(span) click to toggle source

@return [hash]

# File lib/uptrace/trace/exporter.rb, line 79
def uptrace_span(span)
  out = {
    id: span.span_id.unpack1('Q'),
    traceId: span.trace_id,

    name: span.name,
    kind: kind_as_str(span.kind),
    startTime: as_unix_nano(span.start_timestamp),
    endTime: as_unix_nano(span.end_timestamp),

    resource: uptrace_resource(span.resource),
    attrs: span.attributes
  }

  out[:parentId] = span.parent_span_id.unpack1('Q') if span.parent_span_id

  out[:events] = uptrace_events(span.events) unless span.events.nil?
  out[:links] = uptrace_links(span.links) unless span.links.nil?

  status = span.status
  out[:statusCode] = status_code_as_str(status.code)
  out[:statusMessage] = status.description unless status.description.empty?

  il = span.instrumentation_library
  out[:tracerName] = il.name
  out[:tracerVersion] = il.name unless il.version.empty?

  out
end