span.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. # Copyright The OpenTracing Authors
  2. # Copyright Uber Technologies, Inc
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. from __future__ import absolute_import
  16. from . import logs
  17. from opentracing.ext import tags
  18. class SpanContext(object):
  19. """SpanContext represents :class:`Span` state that must propagate to
  20. descendant :class:`Span`\\ s and across process boundaries.
  21. SpanContext is logically divided into two pieces: the user-level "Baggage"
  22. (see :meth:`Span.set_baggage_item` and :meth:`Span.get_baggage_item`) that
  23. propagates across :class:`Span` boundaries and any
  24. tracer-implementation-specific fields that are needed to identify or
  25. otherwise contextualize the associated :class:`Span` (e.g., a ``(trace_id,
  26. span_id, sampled)`` tuple).
  27. """
  28. EMPTY_BAGGAGE = {} # TODO would be nice to make this immutable
  29. @property
  30. def baggage(self):
  31. """
  32. Return baggage associated with this :class:`SpanContext`.
  33. If no baggage has been added to the :class:`Span`, returns an empty
  34. dict.
  35. The caller must not modify the returned dictionary.
  36. See also: :meth:`Span.set_baggage_item()` /
  37. :meth:`Span.get_baggage_item()`
  38. :rtype: dict
  39. :return: baggage associated with this :class:`SpanContext` or ``{}``.
  40. """
  41. return SpanContext.EMPTY_BAGGAGE
  42. class Span(object):
  43. """
  44. Span represents a unit of work executed on behalf of a trace. Examples of
  45. spans include a remote procedure call, or a in-process method call to a
  46. sub-component. Every span in a trace may have zero or more causal parents,
  47. and these relationships transitively form a DAG. It is common for spans to
  48. have at most one parent, and thus most traces are merely tree structures.
  49. Span implements a context manager API that allows the following usage::
  50. with tracer.start_span(operation_name='go_fishing') as span:
  51. call_some_service()
  52. In the context manager syntax it's not necessary to call
  53. :meth:`Span.finish()`
  54. """
  55. def __init__(self, tracer, context):
  56. self._tracer = tracer
  57. self._context = context
  58. @property
  59. def context(self):
  60. """Provides access to the :class:`SpanContext` associated with this
  61. :class:`Span`.
  62. The :class:`SpanContext` contains state that propagates from
  63. :class:`Span` to :class:`Span` in a larger trace.
  64. :rtype: SpanContext
  65. :return: the :class:`SpanContext` associated with this :class:`Span`.
  66. """
  67. return self._context
  68. @property
  69. def tracer(self):
  70. """Provides access to the :class:`Tracer` that created this
  71. :class:`Span`.
  72. :rtype: Tracer
  73. :return: the :class:`Tracer` that created this :class:`Span`.
  74. """
  75. return self._tracer
  76. def set_operation_name(self, operation_name):
  77. """Changes the operation name.
  78. :param operation_name: the new operation name
  79. :type operation_name: str
  80. :rtype: Span
  81. :return: the :class:`Span` itself, for call chaining.
  82. """
  83. return self
  84. def finish(self, finish_time=None):
  85. """Indicates that the work represented by this :class:`Span` has completed or
  86. terminated.
  87. With the exception of the :attr:`Span.context` property, the semantics
  88. of all other :class:`Span` methods are undefined after
  89. :meth:`Span.finish()` has been invoked.
  90. :param finish_time: an explicit :class:`Span` finish timestamp as a
  91. unix timestamp per :meth:`time.time()`
  92. :type finish_time: float
  93. """
  94. pass
  95. def set_tag(self, key, value):
  96. """Attaches a key/value pair to the :class:`Span`.
  97. The value must be a string, a bool, or a numeric type.
  98. If the user calls set_tag multiple times for the same key,
  99. the behavior of the :class:`Tracer` is undefined, i.e. it is
  100. implementation specific whether the :class:`Tracer` will retain the
  101. first value, or the last value, or pick one randomly, or even keep all
  102. of them.
  103. :param key: key or name of the tag. Must be a string.
  104. :type key: str
  105. :param value: value of the tag.
  106. :type value: string or bool or int or float
  107. :rtype: Span
  108. :return: the :class:`Span` itself, for call chaining.
  109. """
  110. return self
  111. def log_kv(self, key_values, timestamp=None):
  112. """Adds a log record to the :class:`Span`.
  113. For example::
  114. span.log_kv({
  115. "event": "time to first byte",
  116. "packet.size": packet.size()})
  117. span.log_kv({"event": "two minutes ago"}, time.time() - 120)
  118. :param key_values: A dict of string keys and values of any type
  119. :type key_values: dict
  120. :param timestamp: A unix timestamp per :meth:`time.time()`; current
  121. time if ``None``
  122. :type timestamp: float
  123. :rtype: Span
  124. :return: the :class:`Span` itself, for call chaining.
  125. """
  126. return self
  127. def set_baggage_item(self, key, value):
  128. """Stores a Baggage item in the :class:`Span` as a key/value pair.
  129. Enables powerful distributed context propagation functionality where
  130. arbitrary application data can be carried along the full path of
  131. request execution throughout the system.
  132. Note 1: Baggage is only propagated to the future (recursive) children
  133. of this :class:`Span`.
  134. Note 2: Baggage is sent in-band with every subsequent local and remote
  135. calls, so this feature must be used with care.
  136. :param key: Baggage item key
  137. :type key: str
  138. :param value: Baggage item value
  139. :type value: str
  140. :rtype: Span
  141. :return: itself, for chaining the calls.
  142. """
  143. return self
  144. def get_baggage_item(self, key):
  145. """Retrieves value of the baggage item with the given key.
  146. :param key: key of the baggage item
  147. :type key: str
  148. :rtype: str
  149. :return: value of the baggage item with given key, or ``None``.
  150. """
  151. return None
  152. def __enter__(self):
  153. """Invoked when :class:`Span` is used as a context manager.
  154. :rtype: Span
  155. :return: the :class:`Span` itself
  156. """
  157. return self
  158. def __exit__(self, exc_type, exc_val, exc_tb):
  159. """Ends context manager and calls finish() on the :class:`Span`.
  160. If exception has occurred during execution, it is automatically logged
  161. and added as a tag. :attr:`~operation.ext.tags.ERROR` will also be set
  162. to `True`.
  163. """
  164. Span._on_error(self, exc_type, exc_val, exc_tb)
  165. self.finish()
  166. @staticmethod
  167. def _on_error(span, exc_type, exc_val, exc_tb):
  168. if not span or not exc_val:
  169. return
  170. span.set_tag(tags.ERROR, True)
  171. span.log_kv({
  172. logs.EVENT: tags.ERROR,
  173. logs.MESSAGE: str(exc_val),
  174. logs.ERROR_OBJECT: exc_val,
  175. logs.ERROR_KIND: exc_type,
  176. logs.STACK: exc_tb,
  177. })
  178. def log_event(self, event, payload=None):
  179. """DEPRECATED"""
  180. if payload is None:
  181. return self.log_kv({logs.EVENT: event})
  182. else:
  183. return self.log_kv({logs.EVENT: event, 'payload': payload})
  184. def log(self, **kwargs):
  185. """DEPRECATED"""
  186. key_values = {}
  187. if logs.EVENT in kwargs:
  188. key_values[logs.EVENT] = kwargs[logs.EVENT]
  189. if 'payload' in kwargs:
  190. key_values['payload'] = kwargs['payload']
  191. timestamp = None
  192. if 'timestamp' in kwargs:
  193. timestamp = kwargs['timestamp']
  194. return self.log_kv(key_values, timestamp)