Skip to content
This repository has been archived by the owner on May 23, 2023. It is now read-only.

expose OpenTracing and API versioning capabilities #16

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 6 additions & 1 deletion opentracing/harness/api_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_start_trace(self):
with tracer.start_trace(operation_name='Fry',
tags={'birthday': 'August 14 1974'}) as span:
span.log_event('birthplace',
payload={'hospital': 'Brooklyn Pre-Med Hospital',
payload={'hospital': 'Brooklyn Pre-Med Hospital',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I delinted and found some spaces/tabs things... probably my copy of vim)

'city': 'Old New York'})
tracer.close()

Expand Down Expand Up @@ -146,3 +146,8 @@ def test_binary_propagation(self):
trace_attributes=bin_attrs
) as reassembled_span:
reassembled_span.set_trace_attribute('middle-name', 'Rodriguez')

def test_implementation_id(self):
impl_id = self.tracer().implementation_id()
assert len(impl_id.name) > 0
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what else to check here...

assert len(impl_id.version) > 0
11 changes: 6 additions & 5 deletions opentracing/propagator.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@

from .span import Span


class SpanPropagator(object):
"""SpanPropagator encodes and decodes Spans between processes.

SpanPropagator is responsible (a) for encoding Span instances in a manner
suitable for propagation, and (b) for taking that encoded data and using it
to generate Span instances that are placed appropriately in the overarching
Trace. Typically the propagation will take place across an RPC boundary, but
message queues and other IPC mechanisms are also reasonable places to use
a SpanPropagator.
Trace. Typically the propagation will take place across an RPC boundary,
but message queues and other IPC mechanisms are also reasonable places to
use a SpanPropagator.

The encoded form of a propagated span is divided into two components:

Expand All @@ -53,7 +54,7 @@ class SpanPropagator(object):

def propagate_span_as_binary(self, span):
"""Represents the Span for propagation as opaque binary data.

:param span: the Span instance to propagate

:rtype: ((bytearray, bytearray) or None)
Expand All @@ -66,7 +67,7 @@ def propagate_span_as_binary(self, span):

def propagate_span_as_text(self, span):
"""Represents the Span for propagation as a pair of text dicts.

:param span: the Span instance to propagate

:rtype: ((dict, dict) or None)
Expand Down
10 changes: 5 additions & 5 deletions opentracing/span.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,19 +119,19 @@ def log_event(self, event, payload=None):
"""Logs an event against the span, with the current timestamp.

:param event: an event name as a string
:param payload: an arbitrary structured payload. Implementations may
choose to ignore none, some, or all of the payload.
:param payload: an arbitrary structured payload. Implementations may
choose to ignore none, some, or all of the payload.
:return: returns the span itself, for chaining the calls
"""
return self

def log(self, **kwargs):
"""Records a generic Log event at an arbitrary timestamp.

:param timestamp: the log timestamp an a unix timestamp per time.time()
:param timestamp: the log timestamp an a unix timestamp per time.time()
:param event: an event name as a string
:param payload: an arbitrary structured payload. Implementations may
choose to ignore none, some, or all of the payload.
:param payload: an arbitrary structured payload. Implementations may
choose to ignore none, some, or all of the payload.
:return: returns the span itself, for chaining the calls
"""
return self
Expand Down
22 changes: 22 additions & 0 deletions opentracing/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from __future__ import absolute_import
from concurrent.futures import Future
from collections import namedtuple
from .span import Span
from .propagator import SpanPropagator

Expand Down Expand Up @@ -82,3 +83,24 @@ def close(self):
fut = Future()
fut.set_result(True)
return fut

def implementation_id(self):
"""Returns the ImplementationID for this OpenTracing implementation.

:return: An ImplementationID instance that describes this OpenTracing
implementation.
"""
return ImplementationID('noop', '1.0.0')


# Per collections.namedtuple, the below defines a class called ImplementationID
# which can be initialized via positional parameters or a kwargs-style
# initializer.
#
# dapper_impl = ImplementationID('dapper', '0.1.2')
# zipkin_impl = ImplementationID(name='zipkin', version='0.3.4')
#
ImplementationID = namedtuple('ImplementationID', 'name version')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like namedtuple (makes me feel like I'm in a language that's not python :), but I could be talked into returning an ad hoc string map, too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sgtm - it's also immutable. Not sure we need the comment in L96-98.


# The SemVer (http://semver.org/) of the OpenTracing Python API.
OPENTRACING_PYTHON_SEMVER = '0.9.0'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bcronin do we need anything fancier than this? I think the biggest challenge will be remembering to update it :-/

Of course we can automate that process to a certain extent...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would put it at the top of the file.

the biggest challenge will be remembering to update it

:-) I remember seeing somewhere a lib was taking a hash of its public API and having a unit test comparing that hash with a constant, and a comment in upper case "if this hash changed the version needs to change". If I have any grey hair, that's when it started.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Preface: I do not know python very well, so this might be just a language misunderstanding on my part...)

What I was imagining was given a Tracer object, there should be some way to query the supported OpenTracing API version of that object. Having a global does not accomplish quite the same thing.

At a high-level the problem is the case (impossible in some setups, but not all) were a single logical application has been bound, for one reason or another, to multiple versions of OpenTracing (via use of third-party libraries, nested dependencies, dynamically loaded code, etc.). This case is important because removing the multiple versions is not always something within an individual developer's control (e.g. nested dependencies and third-party code).

In Node.js for example, as far as I know it is fully possible to have two references to two different versions of the same package within a single logical application or service (there will be two distinct copies of the package within the loaded code; to help imagine the real-world scenario think of an application that relies on a framework that relies on third-party extensions that rely on third-party libraries -- and in that complex dependency tree someone has not updated their code to the latest version of OpenTracing but others have). The version check needs to be done on the actual Tracer object being used - and can't be a global property as the global property only would reflect the version of the locally referenced package, not necessarily the version that created the Tracer object in question.

To clarify this is not Node.js specific, consider a C++ application that dynamically loads a library (i.e. a DLL/.so plugin of some sort) that, as part of the plugin interface, takes a handle to the active Tracer. It may be interested in knowing the version of that runtime Tracer object it has been handed, not the version of OpenTracing the DLL itself was compiled against, to be sure the host application is compatible. A global check would only check what it had been compiled against.

I certainly wouldn't argue these are going to be a common cases but to achieve the intent, I believe the version info has to be a property of the Tracer object, not a package/module/library-level property.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bcronin I see what you're saying. If we're going through the trouble of including this API in the first place, we may as well do it right. I'm not at my laptop right now, but I will think about it... It's annoying that Tracer impls need not actually inherit from Tracer (due to the usual dynamic typing situation)... If that weren't the case, this would be straightforward! Maybe we should just require it in a clearly worded comment or something...