corehttp 1.0.0b6__py3-none-any.whl → 1.0.0b7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- corehttp/_version.py +1 -1
- corehttp/credentials.py +14 -5
- corehttp/instrumentation/__init__.py +9 -0
- corehttp/instrumentation/tracing/__init__.py +14 -0
- corehttp/instrumentation/tracing/_decorator.py +189 -0
- corehttp/instrumentation/tracing/_models.py +72 -0
- corehttp/instrumentation/tracing/_tracer.py +69 -0
- corehttp/instrumentation/tracing/opentelemetry.py +277 -0
- corehttp/instrumentation/tracing/utils.py +31 -0
- corehttp/paging.py +13 -0
- corehttp/rest/_aiohttp.py +17 -5
- corehttp/rest/_http_response_impl.py +7 -7
- corehttp/rest/_http_response_impl_async.py +2 -0
- corehttp/rest/_httpx.py +8 -8
- corehttp/rest/_requests_basic.py +13 -5
- corehttp/rest/_rest_py3.py +2 -2
- corehttp/runtime/pipeline/__init__.py +2 -2
- corehttp/runtime/pipeline/_base.py +2 -1
- corehttp/runtime/pipeline/_base_async.py +2 -0
- corehttp/runtime/pipeline/_tools.py +18 -2
- corehttp/runtime/policies/__init__.py +2 -0
- corehttp/runtime/policies/_authentication.py +28 -5
- corehttp/runtime/policies/_authentication_async.py +22 -3
- corehttp/runtime/policies/_distributed_tracing.py +169 -0
- corehttp/runtime/policies/_retry.py +7 -11
- corehttp/runtime/policies/_retry_async.py +4 -8
- corehttp/runtime/policies/_universal.py +11 -0
- corehttp/serialization.py +236 -2
- corehttp/settings.py +59 -0
- corehttp/transport/_base.py +1 -3
- corehttp/transport/_base_async.py +1 -3
- corehttp/transport/aiohttp/_aiohttp.py +39 -14
- corehttp/transport/requests/_requests_basic.py +31 -16
- corehttp/utils/_utils.py +2 -1
- {corehttp-1.0.0b6.dist-info → corehttp-1.0.0b7.dist-info}/METADATA +52 -6
- corehttp-1.0.0b7.dist-info/RECORD +61 -0
- {corehttp-1.0.0b6.dist-info → corehttp-1.0.0b7.dist-info}/WHEEL +1 -1
- corehttp-1.0.0b6.dist-info/RECORD +0 -52
- {corehttp-1.0.0b6.dist-info → corehttp-1.0.0b7.dist-info/licenses}/LICENSE +0 -0
- {corehttp-1.0.0b6.dist-info → corehttp-1.0.0b7.dist-info}/top_level.txt +0 -0
corehttp/_version.py
CHANGED
corehttp/credentials.py
CHANGED
|
@@ -68,7 +68,11 @@ class TokenCredential(Protocol, ContextManager["TokenCredential"]):
|
|
|
68
68
|
...
|
|
69
69
|
|
|
70
70
|
def close(self) -> None:
|
|
71
|
-
|
|
71
|
+
"""Close the credential, releasing any resources it holds.
|
|
72
|
+
|
|
73
|
+
:return: None
|
|
74
|
+
:rtype: None
|
|
75
|
+
"""
|
|
72
76
|
|
|
73
77
|
|
|
74
78
|
class ServiceNamedKey(NamedTuple):
|
|
@@ -93,7 +97,7 @@ class ServiceKeyCredential:
|
|
|
93
97
|
It provides the ability to update the key without creating a new client.
|
|
94
98
|
|
|
95
99
|
:param str key: The key used to authenticate to a service
|
|
96
|
-
:raises:
|
|
100
|
+
:raises TypeError: If the key is not a string.
|
|
97
101
|
"""
|
|
98
102
|
|
|
99
103
|
def __init__(self, key: str) -> None:
|
|
@@ -117,7 +121,8 @@ class ServiceKeyCredential:
|
|
|
117
121
|
to update long-lived clients.
|
|
118
122
|
|
|
119
123
|
:param str key: The key used to authenticate to a service
|
|
120
|
-
:raises
|
|
124
|
+
:raises ValueError: If the key is None or empty.
|
|
125
|
+
:raises TypeError: If the key is not a string.
|
|
121
126
|
"""
|
|
122
127
|
if not key:
|
|
123
128
|
raise ValueError("The key used for updating can not be None or empty")
|
|
@@ -132,7 +137,7 @@ class ServiceNamedKeyCredential:
|
|
|
132
137
|
|
|
133
138
|
:param str name: The name of the credential used to authenticate to a service.
|
|
134
139
|
:param str key: The key used to authenticate to a service.
|
|
135
|
-
:raises:
|
|
140
|
+
:raises TypeError: If the name or key is not a string.
|
|
136
141
|
"""
|
|
137
142
|
|
|
138
143
|
def __init__(self, name: str, key: str) -> None:
|
|
@@ -180,7 +185,11 @@ class AsyncTokenCredential(Protocol, AsyncContextManager["AsyncTokenCredential"]
|
|
|
180
185
|
...
|
|
181
186
|
|
|
182
187
|
async def close(self) -> None:
|
|
183
|
-
|
|
188
|
+
"""Close the credential, releasing any resources.
|
|
189
|
+
|
|
190
|
+
:return: None
|
|
191
|
+
:rtype: None
|
|
192
|
+
"""
|
|
184
193
|
|
|
185
194
|
async def __aexit__(
|
|
186
195
|
self,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# ------------------------------------
|
|
2
|
+
# Copyright (c) Microsoft Corporation.
|
|
3
|
+
# Licensed under the MIT License.
|
|
4
|
+
# ------------------------------------
|
|
5
|
+
from ._models import SpanKind, Link, TracingOptions
|
|
6
|
+
from ._decorator import distributed_trace, distributed_trace_async
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Link",
|
|
10
|
+
"SpanKind",
|
|
11
|
+
"TracingOptions",
|
|
12
|
+
"distributed_trace",
|
|
13
|
+
"distributed_trace_async",
|
|
14
|
+
]
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# ------------------------------------
|
|
2
|
+
# Copyright (c) Microsoft Corporation.
|
|
3
|
+
# Licensed under the MIT License.
|
|
4
|
+
# ------------------------------------
|
|
5
|
+
"""The decorator to apply if you want the given method traced."""
|
|
6
|
+
from contextvars import ContextVar
|
|
7
|
+
import functools
|
|
8
|
+
from typing import Awaitable, Any, TypeVar, overload, Optional, Callable, TYPE_CHECKING
|
|
9
|
+
from typing_extensions import ParamSpec
|
|
10
|
+
|
|
11
|
+
from ._models import SpanKind
|
|
12
|
+
from ._tracer import get_tracer
|
|
13
|
+
from ...settings import settings
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from ._models import TracingOptions
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
P = ParamSpec("P")
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# This context variable is used to determine if we are already in the span context of a decorated function.
|
|
24
|
+
_in_span_context = ContextVar("in_span_context", default=False)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@overload
|
|
28
|
+
def distributed_trace(__func: Callable[P, T]) -> Callable[P, T]:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@overload
|
|
33
|
+
def distributed_trace(**kwargs: Any) -> Callable[[Callable[P, T]], Callable[P, T]]:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def distributed_trace(__func: Optional[Callable[P, T]] = None, **kwargs: Any) -> Any: # pylint: disable=unused-argument
|
|
38
|
+
"""Decorator to apply to an SDK method to have it traced automatically.
|
|
39
|
+
|
|
40
|
+
Span will use the method's qualified name.
|
|
41
|
+
|
|
42
|
+
Note: This decorator SHOULD NOT be used by application developers. It's intended to be called by client
|
|
43
|
+
libraries only. Application developers should use OpenTelemetry directly to instrument their applications.
|
|
44
|
+
|
|
45
|
+
:param callable __func: A function to decorate
|
|
46
|
+
|
|
47
|
+
:return: The decorated function
|
|
48
|
+
:rtype: Any
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def decorator(func: Callable[P, T]) -> Callable[P, T]:
|
|
52
|
+
|
|
53
|
+
@functools.wraps(func)
|
|
54
|
+
def wrapper_use_tracer(*args: Any, **kwargs: Any) -> T:
|
|
55
|
+
# If we are already in the span context of a decorated function, don't trace.
|
|
56
|
+
if _in_span_context.get():
|
|
57
|
+
return func(*args, **kwargs)
|
|
58
|
+
|
|
59
|
+
# This will be popped in the pipeline or transport runner.
|
|
60
|
+
tracing_options: TracingOptions = kwargs.get("tracing_options", {})
|
|
61
|
+
|
|
62
|
+
# User can explicitly disable tracing for this call
|
|
63
|
+
user_enabled = tracing_options.get("enabled")
|
|
64
|
+
if user_enabled is False:
|
|
65
|
+
return func(*args, **kwargs)
|
|
66
|
+
|
|
67
|
+
# If tracing is disabled globally and user didn't explicitly enable it, don't trace.
|
|
68
|
+
if not settings.tracing_enabled and user_enabled is None:
|
|
69
|
+
return func(*args, **kwargs)
|
|
70
|
+
|
|
71
|
+
config = {}
|
|
72
|
+
if args and hasattr(args[0], "_instrumentation_config"):
|
|
73
|
+
config = args[0]._instrumentation_config # pylint: disable=protected-access
|
|
74
|
+
|
|
75
|
+
method_tracer = get_tracer(
|
|
76
|
+
library_name=config.get("library_name"),
|
|
77
|
+
library_version=config.get("library_version"),
|
|
78
|
+
schema_url=config.get("schema_url"),
|
|
79
|
+
attributes=config.get("attributes"),
|
|
80
|
+
)
|
|
81
|
+
if not method_tracer:
|
|
82
|
+
return func(*args, **kwargs)
|
|
83
|
+
|
|
84
|
+
name = func.__qualname__
|
|
85
|
+
span_suppression_token = _in_span_context.set(True)
|
|
86
|
+
try:
|
|
87
|
+
with method_tracer.start_as_current_span(
|
|
88
|
+
name=name,
|
|
89
|
+
kind=SpanKind.INTERNAL,
|
|
90
|
+
attributes=tracing_options.get("attributes"),
|
|
91
|
+
) as span:
|
|
92
|
+
try:
|
|
93
|
+
return func(*args, **kwargs)
|
|
94
|
+
except Exception as err: # pylint: disable=broad-except
|
|
95
|
+
ex_type = type(err)
|
|
96
|
+
module = ex_type.__module__ if ex_type.__module__ != "builtins" else ""
|
|
97
|
+
error_type = f"{module}.{ex_type.__qualname__}" if module else ex_type.__qualname__
|
|
98
|
+
span.set_attribute("error.type", error_type)
|
|
99
|
+
raise
|
|
100
|
+
finally:
|
|
101
|
+
_in_span_context.reset(span_suppression_token)
|
|
102
|
+
|
|
103
|
+
return wrapper_use_tracer
|
|
104
|
+
|
|
105
|
+
return decorator if __func is None else decorator(__func)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@overload
|
|
109
|
+
def distributed_trace_async(__func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@overload
|
|
114
|
+
def distributed_trace_async(**kwargs: Any) -> Callable[[Callable[P, Awaitable[T]]], Callable[P, Awaitable[T]]]:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def distributed_trace_async( # pylint: disable=unused-argument
|
|
119
|
+
__func: Optional[Callable[P, Awaitable[T]]] = None,
|
|
120
|
+
**kwargs: Any,
|
|
121
|
+
) -> Any:
|
|
122
|
+
"""Decorator to apply to an SDK method to have it traced automatically.
|
|
123
|
+
|
|
124
|
+
Span will use the method's qualified name.
|
|
125
|
+
|
|
126
|
+
Note: This decorator SHOULD NOT be used by application developers. It's intended to be called by client
|
|
127
|
+
libraries only. Application developers should use OpenTelemetry directly to instrument their applications.
|
|
128
|
+
|
|
129
|
+
:param callable __func: A function to decorate
|
|
130
|
+
|
|
131
|
+
:return: The decorated function
|
|
132
|
+
:rtype: Any
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def decorator(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
|
|
136
|
+
|
|
137
|
+
@functools.wraps(func)
|
|
138
|
+
async def wrapper_use_tracer(*args: Any, **kwargs: Any) -> T:
|
|
139
|
+
# If we are already in the span context of a decorated function, don't trace.
|
|
140
|
+
if _in_span_context.get():
|
|
141
|
+
return await func(*args, **kwargs)
|
|
142
|
+
|
|
143
|
+
# This will be popped in the pipeline or transport runner.
|
|
144
|
+
tracing_options: TracingOptions = kwargs.get("tracing_options", {})
|
|
145
|
+
|
|
146
|
+
# User can explicitly disable tracing for this call
|
|
147
|
+
user_enabled = tracing_options.get("enabled")
|
|
148
|
+
if user_enabled is False:
|
|
149
|
+
return await func(*args, **kwargs)
|
|
150
|
+
|
|
151
|
+
# If tracing is disabled globally and user didn't explicitly enable it, don't trace.
|
|
152
|
+
if not settings.tracing_enabled and user_enabled is None:
|
|
153
|
+
return await func(*args, **kwargs)
|
|
154
|
+
|
|
155
|
+
config = {}
|
|
156
|
+
if args and hasattr(args[0], "_instrumentation_config"):
|
|
157
|
+
config = args[0]._instrumentation_config # pylint: disable=protected-access
|
|
158
|
+
|
|
159
|
+
method_tracer = get_tracer(
|
|
160
|
+
library_name=config.get("library_name"),
|
|
161
|
+
library_version=config.get("library_version"),
|
|
162
|
+
schema_url=config.get("schema_url"),
|
|
163
|
+
attributes=config.get("attributes"),
|
|
164
|
+
)
|
|
165
|
+
if not method_tracer:
|
|
166
|
+
return await func(*args, **kwargs)
|
|
167
|
+
|
|
168
|
+
name = func.__qualname__
|
|
169
|
+
span_suppression_token = _in_span_context.set(True)
|
|
170
|
+
try:
|
|
171
|
+
with method_tracer.start_as_current_span(
|
|
172
|
+
name=name,
|
|
173
|
+
kind=SpanKind.INTERNAL,
|
|
174
|
+
attributes=tracing_options.get("attributes"),
|
|
175
|
+
) as span:
|
|
176
|
+
try:
|
|
177
|
+
return await func(*args, **kwargs)
|
|
178
|
+
except Exception as err: # pylint: disable=broad-except
|
|
179
|
+
ex_type = type(err)
|
|
180
|
+
module = ex_type.__module__ if ex_type.__module__ != "builtins" else ""
|
|
181
|
+
error_type = f"{module}.{ex_type.__qualname__}" if module else ex_type.__qualname__
|
|
182
|
+
span.set_attribute("error.type", error_type)
|
|
183
|
+
raise
|
|
184
|
+
finally:
|
|
185
|
+
_in_span_context.reset(span_suppression_token)
|
|
186
|
+
|
|
187
|
+
return wrapper_use_tracer
|
|
188
|
+
|
|
189
|
+
return decorator if __func is None else decorator(__func)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# ------------------------------------
|
|
2
|
+
# Copyright (c) Microsoft Corporation.
|
|
3
|
+
# Licensed under the MIT License.
|
|
4
|
+
# ------------------------------------
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Dict, Mapping, Optional, Union, Sequence, TypedDict
|
|
8
|
+
|
|
9
|
+
from ...utils import CaseInsensitiveEnumMeta
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
AttributeValue = Union[
|
|
13
|
+
str,
|
|
14
|
+
bool,
|
|
15
|
+
int,
|
|
16
|
+
float,
|
|
17
|
+
Sequence[str],
|
|
18
|
+
Sequence[bool],
|
|
19
|
+
Sequence[int],
|
|
20
|
+
Sequence[float],
|
|
21
|
+
]
|
|
22
|
+
Attributes = Mapping[str, AttributeValue]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SpanKind(Enum, metaclass=CaseInsensitiveEnumMeta):
|
|
26
|
+
"""Describes the role or kind of a span within a distributed trace.
|
|
27
|
+
|
|
28
|
+
This helps to categorize spans based on their relationship to other spans and the type
|
|
29
|
+
of operation they represent.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
UNSPECIFIED = 1
|
|
33
|
+
"""Unspecified span kind."""
|
|
34
|
+
|
|
35
|
+
SERVER = 2
|
|
36
|
+
"""Indicates that the span describes an operation that handles a remote request."""
|
|
37
|
+
|
|
38
|
+
CLIENT = 3
|
|
39
|
+
"""Indicates that the span describes a request to some remote service."""
|
|
40
|
+
|
|
41
|
+
PRODUCER = 4
|
|
42
|
+
"""Indicates that the span describes the initiation or scheduling of a local or remote operation."""
|
|
43
|
+
|
|
44
|
+
CONSUMER = 5
|
|
45
|
+
"""Indicates that the span represents the processing of an operation initiated by a producer."""
|
|
46
|
+
|
|
47
|
+
INTERNAL = 6
|
|
48
|
+
"""Indicates that the span is used internally in the application."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Link:
|
|
52
|
+
"""Represents a reference from one span to another span.
|
|
53
|
+
|
|
54
|
+
:param headers: A dictionary of the request header as key value pairs.
|
|
55
|
+
:type headers: dict
|
|
56
|
+
:param attributes: Any additional attributes that should be added to link
|
|
57
|
+
:type attributes: dict
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, headers: Dict[str, str], attributes: Optional[Attributes] = None) -> None:
|
|
61
|
+
self.headers = headers
|
|
62
|
+
self.attributes = attributes
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TracingOptions(TypedDict, total=False):
|
|
66
|
+
"""Options to configure tracing behavior for operations."""
|
|
67
|
+
|
|
68
|
+
enabled: bool
|
|
69
|
+
"""Whether tracing is enabled for the operation. By default, if the global setting is enabled, tracing is
|
|
70
|
+
enabled for all operations. This option can be used to override the global setting for a specific operation."""
|
|
71
|
+
attributes: Attributes
|
|
72
|
+
"""Attributes to include in the spans emitted for the operation."""
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# ------------------------------------
|
|
2
|
+
# Copyright (c) Microsoft Corporation.
|
|
3
|
+
# Licensed under the MIT License.
|
|
4
|
+
# ------------------------------------
|
|
5
|
+
from typing import Optional, Union, Mapping, TYPE_CHECKING
|
|
6
|
+
from functools import lru_cache
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
try:
|
|
10
|
+
from .opentelemetry import OpenTelemetryTracer
|
|
11
|
+
except ImportError:
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _get_tracer_impl():
|
|
16
|
+
# Check if OpenTelemetry is available/installed.
|
|
17
|
+
try:
|
|
18
|
+
from .opentelemetry import OpenTelemetryTracer
|
|
19
|
+
|
|
20
|
+
return OpenTelemetryTracer
|
|
21
|
+
except ImportError:
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@lru_cache
|
|
26
|
+
def _get_tracer_cached(
|
|
27
|
+
library_name: Optional[str],
|
|
28
|
+
library_version: Optional[str],
|
|
29
|
+
schema_url: Optional[str],
|
|
30
|
+
attributes_key: Optional[frozenset],
|
|
31
|
+
) -> Optional["OpenTelemetryTracer"]:
|
|
32
|
+
tracer_impl = _get_tracer_impl()
|
|
33
|
+
if tracer_impl:
|
|
34
|
+
# Convert attributes_key back to dict if needed
|
|
35
|
+
attributes = dict(attributes_key) if attributes_key else None
|
|
36
|
+
return tracer_impl(
|
|
37
|
+
library_name=library_name,
|
|
38
|
+
library_version=library_version,
|
|
39
|
+
schema_url=schema_url,
|
|
40
|
+
attributes=attributes,
|
|
41
|
+
)
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_tracer(
|
|
46
|
+
*,
|
|
47
|
+
library_name: Optional[str] = None,
|
|
48
|
+
library_version: Optional[str] = None,
|
|
49
|
+
schema_url: Optional[str] = None,
|
|
50
|
+
attributes: Optional[Mapping[str, Union[str, bool, int, float]]] = None,
|
|
51
|
+
) -> Optional["OpenTelemetryTracer"]:
|
|
52
|
+
"""Get the OpenTelemetry tracer instance if available.
|
|
53
|
+
|
|
54
|
+
If OpenTelemetry is not available, this method will return None. This method caches
|
|
55
|
+
the tracer instance for each unique set of parameters.
|
|
56
|
+
|
|
57
|
+
:keyword library_name: The name of the library to use in the tracer.
|
|
58
|
+
:paramtype library_name: str
|
|
59
|
+
:keyword library_version: The version of the library to use in the tracer.
|
|
60
|
+
:paramtype library_version: str
|
|
61
|
+
:keyword schema_url: Specifies the Schema URL of the emitted spans.
|
|
62
|
+
:paramtype schema_url: str
|
|
63
|
+
:keyword attributes: Attributes to add to the emitted spans.
|
|
64
|
+
:paramtype attributes: Mapping[str, Union[str, bool, int, float]]
|
|
65
|
+
:return: The OpenTelemetry tracer instance if available.
|
|
66
|
+
:rtype: Optional[~corehttp.instrumentation.tracing.opentelemetry.OpenTelemetryTracer]
|
|
67
|
+
"""
|
|
68
|
+
attributes_key = frozenset(attributes.items()) if attributes else None
|
|
69
|
+
return _get_tracer_cached(library_name, library_version, schema_url, attributes_key)
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# ------------------------------------
|
|
2
|
+
# Copyright (c) Microsoft Corporation.
|
|
3
|
+
# Licensed under the MIT License.
|
|
4
|
+
# ------------------------------------
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from contextvars import Token
|
|
8
|
+
from typing import Any, Optional, Dict, Sequence, cast, Callable, Iterator, TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from opentelemetry import context as otel_context_module, trace
|
|
11
|
+
from opentelemetry.trace import (
|
|
12
|
+
Span,
|
|
13
|
+
SpanKind as OpenTelemetrySpanKind,
|
|
14
|
+
Link as OpenTelemetryLink,
|
|
15
|
+
StatusCode,
|
|
16
|
+
)
|
|
17
|
+
from opentelemetry.trace.propagation import get_current_span as get_current_span_otel
|
|
18
|
+
from opentelemetry.propagate import extract, inject
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY # type: ignore[attr-defined]
|
|
22
|
+
except ImportError:
|
|
23
|
+
_SUPPRESS_HTTP_INSTRUMENTATION_KEY = "suppress_http_instrumentation"
|
|
24
|
+
|
|
25
|
+
from ..._version import VERSION
|
|
26
|
+
from ._models import (
|
|
27
|
+
Attributes,
|
|
28
|
+
SpanKind as _SpanKind,
|
|
29
|
+
Link as _Link,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from corehttp.instrumentation.tracing import Link, SpanKind
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
_DEFAULT_SCHEMA_URL = "https://opentelemetry.io/schemas/1.23.1"
|
|
37
|
+
_DEFAULT_MODULE_NAME = "corehttp"
|
|
38
|
+
|
|
39
|
+
_KIND_MAPPINGS = {
|
|
40
|
+
_SpanKind.CLIENT: OpenTelemetrySpanKind.CLIENT,
|
|
41
|
+
_SpanKind.CONSUMER: OpenTelemetrySpanKind.CONSUMER,
|
|
42
|
+
_SpanKind.PRODUCER: OpenTelemetrySpanKind.PRODUCER,
|
|
43
|
+
_SpanKind.SERVER: OpenTelemetrySpanKind.SERVER,
|
|
44
|
+
_SpanKind.INTERNAL: OpenTelemetrySpanKind.INTERNAL,
|
|
45
|
+
_SpanKind.UNSPECIFIED: OpenTelemetrySpanKind.INTERNAL,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class OpenTelemetryTracer:
|
|
50
|
+
"""A tracer that uses OpenTelemetry to trace operations.
|
|
51
|
+
|
|
52
|
+
:keyword library_name: The name of the library to use in the tracer.
|
|
53
|
+
:paramtype library_name: str
|
|
54
|
+
:keyword library_version: The version of the library to use in the tracer.
|
|
55
|
+
:paramtype library_version: str
|
|
56
|
+
:keyword schema_url: Specifies the Schema URL of the emitted spans. Defaults to
|
|
57
|
+
"https://opentelemetry.io/schemas/1.23.1".
|
|
58
|
+
:paramtype schema_url: str
|
|
59
|
+
:keyword attributes: Attributes to add to the emitted spans.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
*,
|
|
65
|
+
library_name: Optional[str] = None,
|
|
66
|
+
library_version: Optional[str] = None,
|
|
67
|
+
schema_url: Optional[str] = None,
|
|
68
|
+
attributes: Optional[Attributes] = None,
|
|
69
|
+
) -> None:
|
|
70
|
+
self._tracer = trace.get_tracer(
|
|
71
|
+
instrumenting_module_name=library_name or _DEFAULT_MODULE_NAME,
|
|
72
|
+
instrumenting_library_version=library_version or VERSION,
|
|
73
|
+
schema_url=schema_url or _DEFAULT_SCHEMA_URL,
|
|
74
|
+
attributes=attributes,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def start_span(
|
|
78
|
+
self,
|
|
79
|
+
name: str,
|
|
80
|
+
*,
|
|
81
|
+
kind: SpanKind = _SpanKind.INTERNAL,
|
|
82
|
+
attributes: Optional[Attributes] = None,
|
|
83
|
+
links: Optional[Sequence[Link]] = None,
|
|
84
|
+
start_time: Optional[int] = None,
|
|
85
|
+
context: Optional[Dict[str, Any]] = None,
|
|
86
|
+
) -> Span:
|
|
87
|
+
"""Starts a span without setting it as the current span in the context.
|
|
88
|
+
|
|
89
|
+
:param name: The name of the span
|
|
90
|
+
:type name: str
|
|
91
|
+
:keyword kind: The kind of the span. INTERNAL by default.
|
|
92
|
+
:paramtype kind: ~corehttp.instrumentation.tracing.SpanKind
|
|
93
|
+
:keyword attributes: Attributes to add to the span.
|
|
94
|
+
:paramtype attributes: Mapping[str, AttributeValue]
|
|
95
|
+
:keyword links: Links to add to the span.
|
|
96
|
+
:paramtype links: list[~corehttp.instrumentation.tracing.Link]
|
|
97
|
+
:keyword start_time: The start time of the span in nanoseconds since the epoch.
|
|
98
|
+
:paramtype start_time: Optional[int]
|
|
99
|
+
:keyword context: A dictionary of context values corresponding to the parent span. If not provided,
|
|
100
|
+
the current global context will be used.
|
|
101
|
+
:paramtype context: Optional[Dict[str, any]]
|
|
102
|
+
:return: The span that was started
|
|
103
|
+
:rtype: ~opentelemetry.trace.Span
|
|
104
|
+
"""
|
|
105
|
+
otel_kind = _KIND_MAPPINGS.get(kind, OpenTelemetrySpanKind.INTERNAL)
|
|
106
|
+
otel_links = self._parse_links(links)
|
|
107
|
+
|
|
108
|
+
otel_context = None
|
|
109
|
+
if context:
|
|
110
|
+
otel_context = extract(context)
|
|
111
|
+
|
|
112
|
+
otel_span = self._tracer.start_span(
|
|
113
|
+
name,
|
|
114
|
+
context=otel_context,
|
|
115
|
+
kind=otel_kind,
|
|
116
|
+
attributes=attributes,
|
|
117
|
+
links=otel_links,
|
|
118
|
+
start_time=start_time,
|
|
119
|
+
record_exception=False,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return otel_span
|
|
123
|
+
|
|
124
|
+
@contextmanager
|
|
125
|
+
def start_as_current_span(
|
|
126
|
+
self,
|
|
127
|
+
name: str,
|
|
128
|
+
*,
|
|
129
|
+
kind: SpanKind = _SpanKind.INTERNAL,
|
|
130
|
+
attributes: Optional[Attributes] = None,
|
|
131
|
+
links: Optional[Sequence[Link]] = None,
|
|
132
|
+
start_time: Optional[int] = None,
|
|
133
|
+
context: Optional[Dict[str, Any]] = None,
|
|
134
|
+
end_on_exit: bool = True,
|
|
135
|
+
) -> Iterator[Span]:
|
|
136
|
+
"""Context manager that starts a span and sets it as the current span in the context.
|
|
137
|
+
|
|
138
|
+
.. code:: python
|
|
139
|
+
|
|
140
|
+
with tracer.start_as_current_span("span_name") as span:
|
|
141
|
+
# Do something with the span
|
|
142
|
+
span.set_attribute("key", "value")
|
|
143
|
+
|
|
144
|
+
:param name: The name of the span
|
|
145
|
+
:type name: str
|
|
146
|
+
:keyword kind: The kind of the span. INTERNAL by default.
|
|
147
|
+
:paramtype kind: ~corehttp.instrumentation.tracing.SpanKind
|
|
148
|
+
:keyword attributes: Attributes to add to the span.
|
|
149
|
+
:paramtype attributes: Optional[Attributes]
|
|
150
|
+
:keyword links: Links to add to the span.
|
|
151
|
+
:paramtype links: Optional[Sequence[Link]]
|
|
152
|
+
:keyword start_time: The start time of the span in nanoseconds since the epoch.
|
|
153
|
+
:paramtype start_time: Optional[int]
|
|
154
|
+
:keyword context: A dictionary of context values corresponding to the parent span. If not provided,
|
|
155
|
+
the current global context will be used.
|
|
156
|
+
:paramtype context: Optional[Dict[str, any]]
|
|
157
|
+
:keyword end_on_exit: Whether to end the span when exiting the context manager. Defaults to True.
|
|
158
|
+
:paramtype end_on_exit: bool
|
|
159
|
+
:return: The span that was started
|
|
160
|
+
:rtype: Iterator[~opentelemetry.trace.Span]
|
|
161
|
+
"""
|
|
162
|
+
span = self.start_span(
|
|
163
|
+
name, kind=kind, attributes=attributes, links=links, start_time=start_time, context=context
|
|
164
|
+
)
|
|
165
|
+
with trace.use_span( # pylint: disable=not-context-manager
|
|
166
|
+
span, record_exception=False, end_on_exit=end_on_exit
|
|
167
|
+
) as span:
|
|
168
|
+
yield span
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
@contextmanager
|
|
172
|
+
def use_span(cls, span: Span, *, end_on_exit: bool = True) -> Iterator[Span]:
|
|
173
|
+
"""Context manager that takes a non-active span and activates it in the current context.
|
|
174
|
+
|
|
175
|
+
:param span: The span to set as the current span
|
|
176
|
+
:type span: ~opentelemetry.trace.Span
|
|
177
|
+
:keyword end_on_exit: Whether to end the span when exiting the context manager. Defaults to True.
|
|
178
|
+
:paramtype end_on_exit: bool
|
|
179
|
+
:return: The span that was activated.
|
|
180
|
+
:rtype: Iterator[~opentelemetry.trace.Span]
|
|
181
|
+
"""
|
|
182
|
+
with trace.use_span( # pylint: disable=not-context-manager
|
|
183
|
+
span, record_exception=False, end_on_exit=end_on_exit
|
|
184
|
+
) as active_span:
|
|
185
|
+
yield active_span
|
|
186
|
+
|
|
187
|
+
@staticmethod
|
|
188
|
+
def set_span_error_status(span: Span, description: Optional[str] = None) -> None:
|
|
189
|
+
"""Set the status of a span to ERROR with the provided description, if any.
|
|
190
|
+
|
|
191
|
+
:param span: The span to set the ERROR status on.
|
|
192
|
+
:type span: ~opentelemetry.trace.Span
|
|
193
|
+
:param description: An optional description of the error.
|
|
194
|
+
:type description: str
|
|
195
|
+
"""
|
|
196
|
+
span.set_status(StatusCode.ERROR, description=description)
|
|
197
|
+
|
|
198
|
+
def _parse_links(self, links: Optional[Sequence[Link]]) -> Optional[Sequence[OpenTelemetryLink]]:
|
|
199
|
+
if not links:
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
otel_links = []
|
|
204
|
+
for link in links:
|
|
205
|
+
ctx = extract(link.headers)
|
|
206
|
+
span_ctx = get_current_span_otel(ctx).get_span_context()
|
|
207
|
+
otel_links.append(OpenTelemetryLink(span_ctx, link.attributes))
|
|
208
|
+
return otel_links
|
|
209
|
+
except AttributeError:
|
|
210
|
+
# We will just send the links as is if it's not ~corehttp.instrumentation.tracing.Link without
|
|
211
|
+
# any validation assuming the user knows what they are doing.
|
|
212
|
+
return cast(Sequence[OpenTelemetryLink], links)
|
|
213
|
+
|
|
214
|
+
@classmethod
|
|
215
|
+
def get_current_span(cls) -> Span:
|
|
216
|
+
"""Returns the current span in the context.
|
|
217
|
+
|
|
218
|
+
:return: The current span
|
|
219
|
+
:rtype: ~opentelemetry.trace.Span
|
|
220
|
+
"""
|
|
221
|
+
return get_current_span_otel()
|
|
222
|
+
|
|
223
|
+
@classmethod
|
|
224
|
+
def with_current_context(cls, func: Callable) -> Callable:
|
|
225
|
+
"""Passes the current spans to the new context the function will be run in.
|
|
226
|
+
|
|
227
|
+
:param func: The function that will be run in the new context
|
|
228
|
+
:type func: callable
|
|
229
|
+
:return: The wrapped function
|
|
230
|
+
:rtype: callable
|
|
231
|
+
"""
|
|
232
|
+
current_context = otel_context_module.get_current()
|
|
233
|
+
|
|
234
|
+
def call_with_current_context(*args, **kwargs):
|
|
235
|
+
token = None
|
|
236
|
+
try:
|
|
237
|
+
token = otel_context_module.attach(current_context)
|
|
238
|
+
return func(*args, **kwargs)
|
|
239
|
+
finally:
|
|
240
|
+
if token is not None:
|
|
241
|
+
otel_context_module.detach(token)
|
|
242
|
+
|
|
243
|
+
return call_with_current_context
|
|
244
|
+
|
|
245
|
+
@classmethod
|
|
246
|
+
def get_trace_context(cls) -> Dict[str, str]:
|
|
247
|
+
"""Returns the Trace Context header values associated with the current span.
|
|
248
|
+
|
|
249
|
+
These are generally the W3C Trace Context headers (i.e. "traceparent" and "tracestate").
|
|
250
|
+
:return: A key value pair dictionary
|
|
251
|
+
:rtype: dict[str, str]
|
|
252
|
+
"""
|
|
253
|
+
trace_context: Dict[str, str] = {}
|
|
254
|
+
inject(trace_context)
|
|
255
|
+
return trace_context
|
|
256
|
+
|
|
257
|
+
@classmethod
|
|
258
|
+
def _suppress_auto_http_instrumentation(cls) -> Token:
|
|
259
|
+
"""Enabled automatic HTTP instrumentation suppression.
|
|
260
|
+
|
|
261
|
+
Since corehttp already instruments HTTP calls, we need to suppress any automatic HTTP
|
|
262
|
+
instrumentation provided by other libraries to prevent duplicate spans. This has no effect if no
|
|
263
|
+
automatic HTTP instrumentation libraries are being used.
|
|
264
|
+
|
|
265
|
+
:return: A token that can be used to detach the suppression key from the context
|
|
266
|
+
:rtype: ~contextvars.Token
|
|
267
|
+
"""
|
|
268
|
+
return otel_context_module.attach(otel_context_module.set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True))
|
|
269
|
+
|
|
270
|
+
@classmethod
|
|
271
|
+
def _detach_from_context(cls, token: Token) -> None:
|
|
272
|
+
"""Detach a token from the context.
|
|
273
|
+
|
|
274
|
+
:param token: The token to detach
|
|
275
|
+
:type token: ~contextvars.Token
|
|
276
|
+
"""
|
|
277
|
+
otel_context_module.detach(token)
|