Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,11 @@ In addition to OT compatible methods `Test::Span` provides the following methods

1. `tracer` returns the tracer the span was created by.
1. `in_progress?`, `started?`, `finished?` informs whether the span is in progress, or it's finished.
2. `start_time` returns when the span was started.
2. `end_time` returns when the span was finished, or nil if still in progress.
2. `tags` returns the span's tags.
2. `logs` returns the span's logs, an array of `Test::Span::LogEntry`s.
1. `start_time` returns when the span was started.
1. `end_time` returns when the span was finished, or nil if still in progress.
1. `tags` returns the span's tags.
1. `references` returns SpanContext references to corrent span (either as child_of or followes_from and extracted from OpenTracing::Reference)
1. `logs` returns the span's logs, an array of `Test::Span::LogEntry`s.

The modification operations e.g. `operation_name=`, `set_tag`, `set_baggage_item` on a span are not allowed after it's finished. It throws `Test::Span::SpanAlreadyFinished` exception. The same with `finish`. The span can be finished only once.

Expand All @@ -96,7 +97,7 @@ describe "Test::Span examples" do
let(:span) { tracer.start_span("operation name", tags: {'component' => 'ActiveRecord'}) }

it "is in progress" do
expect(span.in_progress?).to eq(true)
expect(span.in_progress?).to eq(true)
end

it "does have the proper name" do
Expand All @@ -113,7 +114,7 @@ describe "Test::Span examples" do
end

context "when an event was logged" do
let(:span) do
let(:span) do
current_span = tracer.start_span("operation name")
current_span.log(event: "exceptional message", severity: Logger::ERROR, pid: $1)
current_span
Expand Down Expand Up @@ -162,7 +163,7 @@ describe "Test::SpanContext examples" do
let(:tracer) { Test::Tracer.new }

context "when a new span was started as child of root" do
let(:root_context) { tracer.start_span("root span").context }
let(:root_context) { tracer.start_span("root span").context }
let(:child_context) { tracer.start_span("child span", child_of: root_context).context }

it "all have the same trace_id" do
Expand All @@ -176,6 +177,20 @@ describe "Test::SpanContext examples" do
end
```

## Test::ScopeManager

ScopeManager is fully implemented by the tracer, and is also inspired by inspired by [Jaeger](http://jaeger.readthedocs.io/en/latest/). In addition to OT compatible methods `Test::ScopeManager` provides the following methods:

1. `scope_stack` returns the stack of all scope currently, which are not closed.

## Test::Scope

Scope is implemented by the tracer, and is returned from `tracer#start_active_span` and `ScopeManager#activate` It is inspired by [Jaeger](http://jaeger.readthedocs.io/en/latest/). In addition to OT compatible methods `Test::Scope` provides the following methods:

1. `finish_on_close` informs whether the scope's span should be finished on scope closing.
1. `scope_stack` returns reference to scope manager scope stack
1. `closed?` informs whether the scope has already been closed

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
Expand Down
44 changes: 44 additions & 0 deletions lib/test/scope.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

module Test
# Scope represents an OpenTracing Scope
#
# See http://www.opentracing.io for more information.
class Scope
def initialize(span, scope_stack, finish_on_close:)
@span = span
@scope_stack = scope_stack
@finish_on_close = finish_on_close
@closed = false
end

# Return the Span scoped by this Scope
#
# @return [Span]
attr_reader :span, :finish_on_close, :scope_stack

def closed?
@closed
end

# OT complient
# Close scope
#
# Mark the end of the active period for the current thread and Scope,
# updating the ScopeManager#active in the process.
def close
raise "Tried to close already closed span: #{inspect}" if @closed
@closed = true

@span.finish if @finish_on_close
removed_scope = @scope_stack.pop

if removed_scope != self # rubocop:disable Style/GuardClause
raise 'Removed non-active scope, ' \
"removed: #{removed_scope.inspect}, "\
"expected: #{inspect}"
end
removed_scope
end
end
end
49 changes: 49 additions & 0 deletions lib/test/scope_manager.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require_relative 'scope_manager/scope_stack'

module Test
# ScopeManager represents an OpenTracing ScopeManager
#
# See http://www.opentracing.io for more information.
#
# The ScopeManager interface abstracts both the activation of Span instances
# via ScopeManager#activate and access to an active Span/Scope via
# ScopeManager#active
class ScopeManager
attr_reader :scope_stack

def initialize
@scope_stack = ScopeStack.new
end

# OT compliant
# Make a span instance active
#
# @param span [Span] the Span that should become active
# @param finish_on_close [Boolean] whether the Span should automatically be
# finished when Scope#close is called
# @return [Scope] instance to control the end of the active period for the
# Span. It is a programming error to neglect to call Scope#close on the
# returned instance.
def activate(span, finish_on_close: true)
return active if active && active.span == span
scope = Scope.new(span, @scope_stack, finish_on_close: finish_on_close)
@scope_stack.push(scope)
scope
end

# OT compliant
# Return active scope
#
# If there is a non-null Scope, its wrapped Span becomes an implicit parent
# (as Reference#CHILD_OF) of any newly-created Span at
# Tracer#start_active_span or Tracer#start_span time.
#
# @return [Scope] the currently active Scope which can be used to access the
# currently active Span.
def active
@scope_stack.peek
end
end
end
37 changes: 37 additions & 0 deletions lib/test/scope_manager/scope_stack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

module Test
class ScopeManager
# @api private
class ScopeStack
def initialize
# Generate a random identifier to use as the Thread.current key. This is
# needed so that it would be possible to create multiple tracers in one
# thread (mostly useful for testing purposes)
@scope_identifier = IdProvider.generate
end

def push(scope)
store << scope
end

def pop
store.pop
end

def peek
store.last
end

def stack
Thread.current[@scope_identifier].dup
end

private

def store
Thread.current[@scope_identifier] ||= []
end
end
end
end
24 changes: 15 additions & 9 deletions lib/test/span.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@ module Test
class Span < OpenTracing::Span
class SpanAlreadyFinished < StandardError; end

class LogEntry < Struct.new(:event, :timestamp, :fields); end
class LogEntry < Struct.new(:timestamp, :fields); end

include TypeCheck

attr_reader :tracer, :operation_name, :start_time, :end_time, :tags, :logs
attr_reader :tracer, :operation_name, :start_time, :end_time, :tags, :logs, :references

def initialize(tracer:, context:, operation_name:, start_time: Time.now, tags: nil)
def initialize(tracer:, context:, operation_name:, start_time: Time.now, references: [], tags: nil)
Type! tracer, ::Test::Tracer
Type! context, ::Test::SpanContext
Type! operation_name, String
Type! start_time, Time
Type! tags, Hash, NilClass
Type! references, Array, NilClass

@tracer = tracer
@context = context
@operation_name = operation_name
@tags = tags || {}
@logs = []
@references = references
@start_time = start_time
@end_time = nil
@in_progress = true
Expand Down Expand Up @@ -59,21 +61,25 @@ def set_baggage_item(key, value)
Type! value, String, NilClass
ensure_in_progress!

@context.baggage[key] = value.to_s
@context.set_baggage_item(key, value)
self
end

def get_baggage_item(key)
Type! key, String
@context.baggage[key]
@context.get_baggage_item(key)
end

def log(event: nil, timestamp: Time.now, **fields)
Type! event, String, NilClass
Type! timestamp, Time
def log(**args)
Type! args, Hash, NilClass
ensure_in_progress!
log_kv(**args)
end

@logs << LogEntry.new(event, timestamp, fields)
def log_kv(timestamp: Time.now, **fields)
Type! timestamp, Time
ensure_in_progress!
@logs << LogEntry.new(timestamp, fields)
end

def finish(end_time: Time.now)
Expand Down
8 changes: 8 additions & 0 deletions lib/test/span_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ def initialize(trace_id:, span_id:, parent_span_id: nil, baggage: {})
@baggage = baggage
end

def set_baggage_item(key, value)
@baggage[key.to_s] = value.to_s
end

def get_baggage_item(key)
@baggage[key.to_s]
end

def to_s
"SpanContext(trace_id=#{trace_id}, span_id=#{span_id}, parent_span_id=#{parent_span_id}, baggage=#{baggage})"
end
Expand Down
Loading