require 'time'
require 'ddtrace/utils'
require 'ddtrace/ext/errors'
module Datadog
# Represents a logical unit of work in the system. Each trace consists of one or more spans.
# Each span consists of a start time and a duration. For example, a span can describe the time
# spent on a distributed call on a separate machine, or the time spent in a small component
# within a larger operation. Spans can be nested within each other, and in those instances
# will have a parent-child relationship.
class Span
# The max value for a \Span identifier
MAX_ID = 2**64 - 1
attr_accessor :name, :service, :resource, :span_type,
:start_time, :end_time,
:span_id, :trace_id, :parent_id,
:status, :parent
# Create a new span linked to the given tracer. Call the finish() method once the
# tracer operation is over or use the finish_at(time) helper to close the span with the
# given +time+. Available options are:
#
# * +service+: the service name for this span
# * +resource+: the resource this span refers, or +name+ if it's missing
# * +span_type+: the type of the span (such as +http+, +db+ and so on)
# * +parent_id+: the identifier of the parent span
# * +trace_id+: the identifier of the root span for this trace
def initialize(tracer, name, options = {})
@tracer = tracer
@name = name
@service = options.fetch(:service, nil)
@resource = options.fetch(:resource, name)
@span_type = options.fetch(:span_type, nil)
@span_id = Datadog::Utils.next_id()
@parent_id = options.fetch(:parent_id, 0)
@trace_id = options.fetch(:trace_id, @span_id)
@meta = {}
@status = 0
@parent = nil
@start_time = Time.now.utc
@end_time = nil
end
# Set the given key / value tag pair on the span. Keys and values
# must be strings. A valid example is:
#
# span.set_tag('http.method', request.method)
def set_tag(key, value)
@meta[key] = value.to_s
rescue StandardError => e
Datadog::Tracer.log.error("Unable to set the tag #{key}, ignoring it. Caused by: #{e}")
end
# Return the tag wth the given key, nil if it doesn't exist.
def get_tag(key)
@meta[key]
end
# Mark the span with the given error.
def set_error(e)
return if e.nil?
@status = 1
@meta[Datadog::Ext::Errors::MSG] = e.message
@meta[Datadog::Ext::Errors::TYPE] = e.class.to_s
@meta[Datadog::Ext::Errors::STACK] = e.backtrace.join("\n")
end
# Mark the span finished at the current time and submit it.
def finish
finish_at(Time.now.utc)
end
# Mark the span finished at the given time and submit it.
def finish_at(end_time)
@end_time = end_time
@tracer.record(self) unless @tracer.nil?
self
end
# Return whether the span is finished or not.
def finished?
!@end_time.nil?
end
# Return a string representation of the span.
def to_s
"Span(name:#{@name},sid:#{@span_id},tid:#{@trace_id},pid:#{@parent_id})"
end
# Set this span's parent, inheriting any properties not explicitly set.
# If the parent is nil, set the span zero values.
def set_parent(parent)
@parent = parent
if parent.nil?
@trace_id = @span_id
@parent_id = 0
else
@trace_id = parent.trace_id
@parent_id = parent.span_id
@service ||= parent.service
end
end
# Return the hash representation of the current span.
def to_hash
h = {
span_id: @span_id,
parent_id: @parent_id,
trace_id: @trace_id,
name: @name,
service: @service,
resource: @resource,
type: @span_type,
meta: @meta,
error: @status
}
if !@start_time.nil? && !@end_time.nil?
h[:start] = (@start_time.to_f * 1e9).to_i
h[:duration] = ((@end_time - @start_time) * 1e9).to_i
end
h
end
# Return a human readable version of the span
def pretty_print(q)
start_time = (@start_time.to_f * 1e9).to_i rescue '-'
end_time = (@end_time.to_f * 1e9).to_i rescue '-'
duration = ((@end_time - @start_time) * 1e9).to_i rescue 0
q.group 0 do
q.breakable
q.text "Name: #{@name}\n"
q.text "Span ID: #{@span_id}\n"
q.text "Parent ID: #{@parent_id}\n"
q.text "Trace ID: #{@trace_id}\n"
q.text "Type: #{@span_type}\n"
q.text "Service: #{@service}\n"
q.text "Resource: #{@resource}\n"
q.text "Error: #{@status}\n"
q.text "Start: #{start_time}\n"
q.text "End: #{end_time}\n"
q.text "Duration: #{duration}\n"
q.group(2, 'Tags: [', ']') do
q.breakable
q.seplist @meta.each do |key, value|
q.text "#{key} => #{value}"
end
end
end
end
end
end