-
Notifications
You must be signed in to change notification settings - Fork 421
feat(tracer): Support for external observability providers - Tracer #2342
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 42 commits
06c9110
5eab094
cbd75f9
a6c6171
927710c
e6b3a7c
dde7b7f
da8effc
61f5757
0cb8463
7f8bf6c
64dd749
f5fa320
52cab66
ef7c509
f66ad21
2c3f42e
78051f6
bf16f6e
5bc0158
fe9c763
d49e1cc
7fc17b3
5134c36
1b28653
b1a2b34
ed1059b
4639bd4
012f8a8
c9592c5
f0561cc
33e0225
0ac3800
dede79c
844d85b
cedb2af
ee869b3
68ee403
07714e9
8712a30
5fb7990
aeaf6ff
024db25
80739fd
d502dbf
7efe7e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import abc | ||
from contextlib import asynccontextmanager, contextmanager | ||
from typing import Any, AsyncGenerator, Generator, Sequence | ||
|
||
|
||
class BaseSpan(abc.ABC): | ||
"""A span represents a unit of work or operation within a trace. | ||
Spans are the building blocks of Traces.""" | ||
|
||
@abc.abstractmethod | ||
def set_attribute(self, key: str, value: Any, **kwargs) -> None: | ||
"""Set an attribute for a span with a key-value pair. | ||
|
||
Parameters | ||
---------- | ||
key: str | ||
Attribute key | ||
value: Any | ||
Attribute value | ||
kwargs: Optional[dict] | ||
Optional parameters | ||
""" | ||
|
||
@abc.abstractmethod | ||
def record_exception(self, exception: BaseException, **kwargs): | ||
"""Records an exception to this Span. | ||
|
||
Parameters | ||
---------- | ||
exception: Exception | ||
Caught exception during the exectution of this Span | ||
kwargs: Optional[dict] | ||
Optional parameters | ||
""" | ||
|
||
|
||
class BaseProvider(abc.ABC): | ||
roger-zhangg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""BaseProvider is an abstract base class that defines the expected behavior for tracing providers | ||
used by Tracer. Inheriting classes must implement this interface to be compatible with Tracer. | ||
""" | ||
|
||
@abc.abstractmethod | ||
@contextmanager | ||
def trace(self, name: str, **kwargs) -> Generator[BaseSpan, None, None]: | ||
"""Context manager for creating a new span and set it | ||
as the current span in this tracer's context. | ||
|
||
Exiting the context manager will call the span's end method, | ||
as well as return the current span to its previous value by | ||
returning to the previous context. | ||
|
||
Parameters | ||
---------- | ||
name: str | ||
Span name | ||
kwargs: Optional[dict] | ||
Optional parameters to be propagated to the span | ||
""" | ||
|
||
@abc.abstractmethod | ||
@asynccontextmanager | ||
def trace_async(self, name: str, **kwargs) -> AsyncGenerator[BaseSpan, None]: | ||
"""Async Context manager for creating a new span and set it | ||
as the current span in this tracer's context. | ||
|
||
Exiting the context manager will call the span's end method, | ||
as well as return the current span to its previous value by | ||
returning to the previous context. | ||
|
||
Parameters | ||
---------- | ||
name: str | ||
Span name | ||
kwargs: Optional[dict] | ||
Optional parameters to be propagated to the span | ||
""" | ||
|
||
@abc.abstractmethod | ||
def set_attribute(self, key: str, value: Any, **kwargs) -> None: | ||
"""set attribute on current active span with a key-value pair. | ||
|
||
Parameters | ||
---------- | ||
key: str | ||
attribute key | ||
value: Any | ||
attribute value | ||
kwargs: Optional[dict] | ||
Optional parameters to be propagated to the span | ||
""" | ||
|
||
@abc.abstractmethod | ||
def patch(self, modules: Sequence[str]) -> None: | ||
"""Instrument a set of given libraries if supported by provider | ||
See specific provider for more detail | ||
|
||
Exmaple | ||
------- | ||
tracer = Tracer(service="payment") | ||
libraries = (['aioboto3',mysql]) | ||
# provider.patch will be called by tracer.patch | ||
tracer.patch(libraries) | ||
|
||
Parameters | ||
---------- | ||
modules: Set[str] | ||
Set of modules to be patched | ||
""" | ||
|
||
@abc.abstractmethod | ||
def patch_all(self) -> None: | ||
"""Instrument all supported libraries""" |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,141 @@ | ||||||
from __future__ import annotations | ||||||
|
||||||
from contextlib import asynccontextmanager, contextmanager | ||||||
from numbers import Number | ||||||
from typing import Any, AsyncGenerator, Generator, Literal, Sequence, Union | ||||||
|
||||||
from ....shared import constants | ||||||
from ....shared.lazy_import import LazyLoader | ||||||
from ..base import BaseProvider, BaseSpan | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we're trying to use full imports now |
||||||
|
||||||
aws_xray_sdk = LazyLoader(constants.XRAY_SDK_MODULE, globals(), constants.XRAY_SDK_MODULE) | ||||||
|
||||||
|
||||||
class XraySpan(BaseSpan): | ||||||
def __init__(self, subsegment): | ||||||
self.subsegment = subsegment | ||||||
self.add_subsegment = self.subsegment.add_subsegment | ||||||
self.remove_subsegment = self.subsegment.remove_subsegment | ||||||
self.put_annotation = self.subsegment.put_annotation | ||||||
self.put_metadata = self.subsegment.put_metadata | ||||||
self.add_exception = self.subsegment.add_exception | ||||||
self.close = self.subsegment.close | ||||||
|
||||||
def set_attribute( | ||||||
self, | ||||||
key: str, | ||||||
value: Any, | ||||||
category: Literal["Annotation", "Metadata", "Auto"] = "Auto", | ||||||
**kwargs, | ||||||
) -> None: | ||||||
""" | ||||||
Set attribute on this span with a key-value pair. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
Parameters | ||||||
---------- | ||||||
key : str | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
attribute key | ||||||
value : Any | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
Value for attribute | ||||||
category : Literal["Annotation","Metadata","Auto"] = "Auto" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
This parameter specifies the category of attribute to set. | ||||||
- **"Annotation"**: Sets the attribute as an Annotation. | ||||||
- **"Metadata"**: Sets the attribute as Metadata. | ||||||
- **"Auto" (default)**: Automatically determines the attribute | ||||||
type based on its value. | ||||||
|
||||||
kwargs: Optional[dict] | ||||||
Optional parameters to be passed to provider.set_attributes | ||||||
""" | ||||||
if category == "Annotation": | ||||||
self.put_annotation(key=key, value=value) | ||||||
return | ||||||
|
||||||
if category == "Metadata": | ||||||
self.put_metadata(key=key, value=value, namespace=kwargs.get("namespace", "dafault")) | ||||||
return | ||||||
|
||||||
# Auto | ||||||
if isinstance(value, (str, Number, bool)): | ||||||
self.put_annotation(key=key, value=value) | ||||||
return | ||||||
|
||||||
# Auto & not in (str, Number, bool) | ||||||
self.put_metadata(key=key, value=value, namespace=kwargs.get("namespace", "dafault")) | ||||||
|
||||||
def record_exception(self, exception: BaseException, **kwargs): | ||||||
stack = aws_xray_sdk.core.utils.stacktrace.get_stacktrace() | ||||||
self.add_exception(exception=exception, stack=stack) | ||||||
|
||||||
|
||||||
class XrayProvider(BaseProvider): | ||||||
def __init__(self, xray_recorder=None): | ||||||
if not xray_recorder: | ||||||
from aws_xray_sdk.core import xray_recorder | ||||||
self.recorder = xray_recorder | ||||||
self.in_subsegment = self.recorder.in_subsegment | ||||||
self.in_subsegment_async = self.recorder.in_subsegment_async | ||||||
|
||||||
@contextmanager | ||||||
def trace(self, name: str, **kwargs) -> Generator[XraySpan, None, None]: | ||||||
with self.in_subsegment(name=name, **kwargs) as sub_segment: | ||||||
yield XraySpan(subsegment=sub_segment) | ||||||
|
||||||
@asynccontextmanager | ||||||
async def trace_async(self, name: str, **kwargs) -> AsyncGenerator[XraySpan, None]: | ||||||
async with self.in_subsegment_async(name=name, **kwargs) as subsegment: | ||||||
yield XraySpan(subsegment=subsegment) | ||||||
|
||||||
def set_attribute( | ||||||
self, | ||||||
key: str, | ||||||
value: Any, | ||||||
category: Literal["Annotation", "Metadata", "Auto"] = "Auto", | ||||||
**kwargs, | ||||||
) -> None: | ||||||
""" | ||||||
Set attribute on the current active span with a key-value pair. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
Parameters | ||||||
---------- | ||||||
key : str | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
attribute key | ||||||
value : Any | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
Value for attribute | ||||||
category : Literal["Annotation","Metadata","Auto"] = "Auto" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
This parameter specifies the type of attribute to set. | ||||||
- **"Annotation"**: Sets the attribute as an Annotation. | ||||||
- **"Metadata"**: Sets the attribute as Metadata. | ||||||
- **"Auto" (default)**: Automatically determines the attribute | ||||||
type based on its value. | ||||||
|
||||||
kwargs: Optional[dict] | ||||||
Optional parameters to be passed to provider.set_attributes | ||||||
""" | ||||||
if category == "Annotation": | ||||||
self.put_annotation(key=key, value=value) | ||||||
return | ||||||
|
||||||
if category == "Metadata": | ||||||
self.put_metadata(key=key, value=value, namespace=kwargs.get("namespace", "dafault")) | ||||||
return | ||||||
|
||||||
# Auto | ||||||
if isinstance(value, (str, Number, bool)): | ||||||
self.put_annotation(key=key, value=value) | ||||||
return | ||||||
|
||||||
# Auto & not in (str, Number, bool) | ||||||
self.put_metadata(key=key, value=value, namespace=kwargs.get("namespace", "dafault")) | ||||||
|
||||||
def put_annotation(self, key: str, value: Union[str, Number, bool]) -> None: | ||||||
return self.recorder.put_annotation(key=key, value=value) | ||||||
|
||||||
def put_metadata(self, key: str, value: Any, namespace: str = "default") -> None: | ||||||
return self.recorder.put_metadata(key=key, value=value, namespace=namespace) | ||||||
|
||||||
def patch(self, modules: Sequence[str]) -> None: | ||||||
return aws_xray_sdk.core.patch(modules) | ||||||
|
||||||
def patch_all(self) -> None: | ||||||
return aws_xray_sdk.core.patch_all() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a breaking change? Or are we assuming that no one is inheriting from
BaseSegment
today?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will deprecate this in Powertools v3. We're not using this class today, but we never know if customers are, so it's best to revert this change.
Reverted.