lib/instana/tracing/span.rb in instana-0.15.0 vs lib/instana/tracing/span.rb in instana-1.0.1

- old
+ new

@@ -1,53 +1,373 @@ module Instana class Span + REGISTERED_SPANS = [ :rack, :'net-http', :excon ].freeze + ENTRY_SPANS = [ :rack ].freeze + EXIT_SPANS = [ :'net-http', :excon ].freeze + HTTP_SPANS = ENTRY_SPANS + EXIT_SPANS + attr_accessor :parent + attr_accessor :baggage - def initialize(data) - @data = data + def initialize(name, trace_id, parent_id: nil, start_time: Time.now) + @data = {} + @data[:t] = trace_id # Trace ID + @data[:s] = ::Instana::Util.generate_id # Span ID + @data[:p] = parent_id if parent_id # Parent ID + @data[:ta] = :ruby # Agent + @data[:data] = {} + + # Entity Source + @data[:f] = { :e => ::Instana.agent.report_pid, + :h => ::Instana.agent.agent_uuid } + # Start time + @data[:ts] = ::Instana::Util.time_to_ms(start_time) + + @baggage = {} + + # For entry spans, add a backtrace fingerprint + add_stack(limit: 2) if ENTRY_SPANS.include?(name) + + # Attach a backtrace to all exit spans + add_stack if EXIT_SPANS.include?(name) + + # Check for custom tracing + if REGISTERED_SPANS.include?(name.to_sym) + @data[:n] = name.to_sym + else + configure_custom(name) + end end + # Adds a backtrace to this span + # + # @param limit [Integer] Limit the backtrace to the top <limit> frames + # + def add_stack(limit: nil, stack: Kernel.caller) + frame_count = 0 + @data[:stack] = [] + + stack.each do |i| + # If the stack has the full instana gem version in it's path + # then don't include that frame. Also don't exclude the Rack module. + if !i.match(/instana\/instrumentation\/rack.rb/).nil? || + (i.match(::Instana::VERSION_FULL).nil? && i.match('lib/instana/').nil?) + + break if limit && frame_count >= limit + + x = i.split(':') + + @data[:stack] << { + :f => x[0], + :n => x[1], + :m => x[2] + } + frame_count = frame_count + 1 if limit + end + end + end + + # Log an error into the span + # + # @param e [Exception] The exception to be logged + # + def add_error(e) + @data[:error] = true + + if @data.key?(:ec) + @data[:ec] = @data[:ec] + 1 + else + @data[:ec] = 1 + end + + # If a valid exception has been passed in, log the information about it + # In case of just logging an error for things such as HTTP client 5xx + # responses, an exception/backtrace may not exist. + if e + if e.backtrace.is_a?(Array) + add_stack(stack: e.backtrace) + end + + if HTTP_SPANS.include?(@data[:n]) + set_tags(:http => { :error => "#{e.class}: #{e.message}" }) + else + set_tags(:log => { :message => e.message, :parameters => e.class }) + end + e.instance_variable_set(:@instana_logged, true) + end + self + end + + + # Configure this span to be a custom span per the + # SDK generic span type. + # + # @param name [String] name of the span + # @param kvs [Hash] list of key values to be reported in the span + # + def configure_custom(name) + @data[:n] = :sdk + @data[:data] = { :sdk => { :name => name.to_sym } } + + #if kvs.is_a?(Hash) + # @data[:data][:sdk][:type] = kvs.key?(:type) ? kvs[:type] : :local +# +# if kvs.key?(:arguments) +# @data[:data][:sdk][:arguments] = kvs[:arguments] +# end +# +# if kvs.key?(:return) +# @data[:data][:sdk][:return] = kvs[:return] +# end +# @data[:data][:sdk][:custom] = kvs unless kvs.empty? +# end + self + end + + # Closes out the span. This difference between this and + # the finish method tells us how the tracing is being + # performed (with OpenTracing or Instana default) + # + # @param end_time [Time] custom end time, if not now + # @return [Span] + # + def close(end_time = Time.now) + unless end_time.is_a?(Time) + ::Instana.logger.debug "span.close: Passed #{end_time.class} instead of Time class" + end + + @data[:d] = (::Instana::Util.time_to_ms(end_time) - @data[:ts]) + self + end + + ############################################################# + # Accessors + ############################################################# + + # Retrieve the context of this span. + # + # @return [Instana::SpanContext] + # + def context + @context ||= ::Instana::SpanContext.new(@data[:t], @data[:s], @baggage) + end + + # Retrieve the ID for this span + # + # @return [Integer] the span ID def id @data[:s] end + # Retrieve the Trace ID for this span + # + # @return [Integer] the Trace ID + def trace_id + @data[:t] + end + + # Retrieve the parent ID of this span + # + # @return [Integer] parent span ID def parent_id @data[:p] end + # Set the parent ID of this span + # + # @return [Integer] parent span ID + def parent_id=(id) + @data[:p] = id + end + + # Get the name (operation) of this Span + # + # @return [String] or [Symbol] representing the span name def name if custom? @data[:data][:sdk][:name] else @data[:n] end end + # Set the name (operation) for this Span + # + # @params name [String] or [Symbol] + # + def name=(n) + if custom? + @data[:data][:sdk][:name] = n + else + @data[:n] = n + end + end + + # Get the duration value for this Span + # + # @return [Integer] the duration in milliseconds def duration @data[:d] end + # Indicates whether this span in the root span + # in the Trace + # + # @return [Boolean] + # def is_root? @data[:s] == @data[:t] end + # Hash accessor to the internal @data hash + # def [](key) @data[key.to_sym] end + # Hash setter to the internal @data hash + # def []=(key, value) @data[key.to_sym] = value end + # Hash key query to the internal @data hash + # def key?(k) @data.key?(k.to_sym) end + # Get the raw @data hash that summarizes this span + # def raw @data end + # Indicates whether this span is a custom or registered Span def custom? @data[:n] == :sdk + end + + ############################################################# + # OpenTracing Compatibility Methods + ############################################################# + + # Set the name of the operation + # Spec: OpenTracing API + # + # @params name [String] or [Symbol] + # + def operation_name=(name) + @data[:n] = name + end + + # Set a tag value on this span + # Spec: OpenTracing API + # + # @param key [String] the key of the tag + # @param value [String, Numeric, Boolean] the value of the tag. If it's not + # a String, Numeric, or Boolean it will be encoded with to_s + # + def set_tag(key, value) + if custom? + @data[:data][:sdk][:custom] ||= {} + @data[:data][:sdk][:custom][key] = value + else + if !@data[:data].key?(key) + @data[:data][key] = value + elsif value.is_a?(Hash) && self[:data][key].is_a?(Hash) + @data[:data][key].merge!(value) + else + @data[:data][key] = value + end + end + self + end + + # Helper method to add multiple tags to this span + # + # @params tags [Hash] + # @return [Span] + # + def set_tags(tags) + return unless tags.is_a?(Hash) + tags.each do |k,v| + set_tag(k, v) + end + self + end + + # Set a baggage item on the span + # Spec: OpenTracing API + # + # @param key [String] the key of the baggage item + # @param value [String] the value of the baggage item + def set_baggage_item(key, value) + @baggage ||= {} + @baggage[key] = value + + # Init/Update the SpanContext item + if @context + @context.baggage = @baggage + else + @context ||= ::Instana::SpanContext.new(@data[:t], @data[:s], @baggage) + end + self + end + + # Get a baggage item + # Spec: OpenTracing API + # + # @param key [String] the key of the baggage item + # @return Value of the baggage item + # + def get_baggage_item(key) + @baggage[key] + end + + # Retrieve the hash of tags for this span + # + def tags(key = nil) + if custom? + tags = @data[:data][:sdk][:custom] + else + tags = @data[:data][key] + end + key ? tags[key] : tags + end + + # Add a log entry to this span + # Spec: OpenTracing API + # + # @param event [String] event name for the log + # @param timestamp [Time] time of the log + # @param fields [Hash] Additional information to log + # + def log(event = nil, _timestamp = Time.now, **fields) + set_tags(:log => { :message => event, :parameters => fields }) + end + + # Finish the {Span} + # Spec: OpenTracing API + # + # @param end_time [Time] custom end time, if not now + # + def finish(end_time = Time.now) + unless end_time.is_a?(Time) + ::Instana.logger.debug "span.finish: Passed #{end_time.class} instead of Time class" + end + + if ::Instana.tracer.current_span.id != id + ::Instana.logger.tracing "Closing a span that isn't active. This will result in a broken trace: #{self.inspect}" + end + + if is_root? + # This is the root span for the trace. Call log_end to close + # out and queue the trace + ::Instana.tracer.log_end(name, {}, end_time) + else + ::Instana.tracer.current_trace.end_span({}, end_time) + end + self end end end