Skip to content
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

Merge OTELResourceDetector result when creating resources #1096

Merged
merged 6 commits into from
Sep 18, 2020
Merged
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
2 changes: 2 additions & 0 deletions opentelemetry-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Released 2020-09-17
([#1099](https://github.com/open-telemetry/opentelemetry-python/pull/1099))
- Rename members of `trace.sampling.Decision` enum
([#1115](https://github.com/open-telemetry/opentelemetry-python/pull/1115))
- Merge `OTELResourceDetector` result when creating resources
([#1096](https://github.com/open-telemetry/opentelemetry-python/pull/1096))

## Version 0.12b0

Expand Down
76 changes: 72 additions & 4 deletions opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,71 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""
This package implements `OpenTelemetry Resources
<https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/resource/sdk.md#resource-sdk>`_:

*A Resource is an immutable representation of the entity producing
telemetry. For example, a process producing telemetry that is running in
a container on Kubernetes has a Pod name, it is in a namespace and
possibly is part of a Deployment which also has a name. All three of
these attributes can be included in the Resource.*

Resource objects are created with `Resource.create`, which accepts attributes
(key-values). Resource attributes can also be passed at process invocation in
the :envvar:`OTEL_RESOURCE_ATTRIBUTES` environment variable. You should
register your resource with the `opentelemetry.sdk.metrics.MeterProvider` and
`opentelemetry.sdk.trace.TracerProvider` by passing them into their
constructors. The `Resource` passed to a provider is available to the
exporter, which can send on this information as it sees fit.

.. code-block:: python

metrics.set_meter_provider(
MeterProvider(
resource=Resource.create({
"service.name": "shoppingcart",
"service.instance.id": "instance-12",
}),
),
)
print(metrics.get_meter_provider().resource.attributes)

{'telemetry.sdk.language': 'python',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '0.13.dev0',
'service.name': 'shoppingcart',
'service.instance.id': 'instance-12'}

Note that the OpenTelemetry project documents certain `"standard attributes"
<https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/resource/semantic_conventions/README.md>`_
that have prescribed semantic meanings, for example ``service.name`` in the
above example.

.. envvar:: OTEL_RESOURCE_ATTRIBUTES

The :envvar:`OTEL_RESOURCE_ATTRIBUTES` environment variable allows resource
attributes to be passed to the SDK at process invocation. The attributes from
:envvar:`OTEL_RESOURCE_ATTRIBUTES` are merged with those passed to
`Resource.create`, meaning :envvar:`OTEL_RESOURCE_ATTRIBUTES` takes *lower*
priority. Attributes should be in the format ``key1=value1,key2=value2``.
Additional details are available `in the specification
<https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/resource/sdk.md#specifying-resource-information-via-an-environment-variable>`_.

.. code-block:: console

$ OTEL_RESOURCE_ATTRIBUTES="service.name=shoppingcard,will_be_overridden=foo" python - <<EOF
import pprint
from opentelemetry.sdk.resources import Resource
pprint.pprint(Resource.create({"will_be_overridden": "bar"}).attributes)
EOF
{'service.name': 'shoppingcard',
'telemetry.sdk.language': 'python',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '0.13.dev0',
'will_be_overridden': 'bar'}
"""

import abc
import concurrent.futures
import logging
Expand All @@ -33,17 +98,20 @@
OPENTELEMETRY_SDK_VERSION = pkg_resources.get_distribution(
"opentelemetry-sdk"
).version
OTEL_RESOURCE_ATTRIBUTES = "OTEL_RESOURCE_ATTRIBUTES"


class Resource:
def __init__(self, attributes: Attributes):
self._attributes = attributes.copy()

@staticmethod
def create(attributes: Attributes) -> "Resource":
def create(attributes: typing.Optional[Attributes] = None) -> "Resource":
if not attributes:
return _DEFAULT_RESOURCE
return _DEFAULT_RESOURCE.merge(Resource(attributes))
resource = _DEFAULT_RESOURCE
else:
resource = _DEFAULT_RESOURCE.merge(Resource(attributes))
return resource.merge(OTELResourceDetector().detect())

@staticmethod
def create_empty() -> "Resource":
Expand Down Expand Up @@ -92,7 +160,7 @@ def detect(self) -> "Resource":
class OTELResourceDetector(ResourceDetector):
# pylint: disable=no-self-use
def detect(self) -> "Resource":
env_resources_items = os.environ.get("OTEL_RESOURCE_ATTRIBUTES")
env_resources_items = os.environ.get(OTEL_RESOURCE_ATTRIBUTES)
env_resource_map = {}
if env_resources_items:
env_resource_map = {
Expand Down
2 changes: 1 addition & 1 deletion opentelemetry-sdk/tests/metrics/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def test_resource_empty(self):
meter_provider = metrics.MeterProvider()
meter = meter_provider.get_meter(__name__)
# pylint: disable=protected-access
self.assertIs(meter.resource, resources._DEFAULT_RESOURCE)
self.assertEqual(meter.resource, resources._DEFAULT_RESOURCE)

def test_start_pipeline(self):
exporter = mock.Mock()
Expand Down
36 changes: 26 additions & 10 deletions opentelemetry-sdk/tests/resources/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@


class TestResources(unittest.TestCase):
def setUp(self) -> None:
os.environ[resources.OTEL_RESOURCE_ATTRIBUTES] = ""

def tearDown(self) -> None:
os.environ.pop(resources.OTEL_RESOURCE_ATTRIBUTES)

def test_create(self):
attributes = {
"service": "ui",
Expand All @@ -44,14 +50,22 @@ def test_create(self):
self.assertIsInstance(resource, resources.Resource)
self.assertEqual(resource.attributes, expected_attributes)

os.environ[resources.OTEL_RESOURCE_ATTRIBUTES] = "key=value"
resource = resources.Resource.create(attributes)
self.assertIsInstance(resource, resources.Resource)
expected_with_envar = expected_attributes.copy()
expected_with_envar["key"] = "value"
self.assertEqual(resource.attributes, expected_with_envar)
os.environ[resources.OTEL_RESOURCE_ATTRIBUTES] = ""

resource = resources.Resource.create_empty()
self.assertIs(resource, resources._EMPTY_RESOURCE)
self.assertEqual(resource, resources._EMPTY_RESOURCE)

resource = resources.Resource.create(None)
self.assertIs(resource, resources._DEFAULT_RESOURCE)
self.assertEqual(resource, resources._DEFAULT_RESOURCE)

resource = resources.Resource.create({})
self.assertIs(resource, resources._DEFAULT_RESOURCE)
self.assertEqual(resource, resources._DEFAULT_RESOURCE)

def test_resource_merge(self):
left = resources.Resource({"service": "ui"})
Expand Down Expand Up @@ -184,36 +198,38 @@ def test_resource_detector_raise_error(self):

class TestOTELResourceDetector(unittest.TestCase):
def setUp(self) -> None:
os.environ["OTEL_RESOURCE_ATTRIBUTES"] = ""
os.environ[resources.OTEL_RESOURCE_ATTRIBUTES] = ""

def tearDown(self) -> None:
os.environ.pop("OTEL_RESOURCE_ATTRIBUTES")
os.environ.pop(resources.OTEL_RESOURCE_ATTRIBUTES)

def test_empty(self):
detector = resources.OTELResourceDetector()
os.environ["OTEL_RESOURCE_ATTRIBUTES"] = ""
os.environ[resources.OTEL_RESOURCE_ATTRIBUTES] = ""
self.assertEqual(detector.detect(), resources.Resource.create_empty())

def test_one(self):
detector = resources.OTELResourceDetector()
os.environ["OTEL_RESOURCE_ATTRIBUTES"] = "k=v"
os.environ[resources.OTEL_RESOURCE_ATTRIBUTES] = "k=v"
self.assertEqual(detector.detect(), resources.Resource({"k": "v"}))

def test_one_with_whitespace(self):
detector = resources.OTELResourceDetector()
os.environ["OTEL_RESOURCE_ATTRIBUTES"] = " k = v "
os.environ[resources.OTEL_RESOURCE_ATTRIBUTES] = " k = v "
self.assertEqual(detector.detect(), resources.Resource({"k": "v"}))

def test_multiple(self):
detector = resources.OTELResourceDetector()
os.environ["OTEL_RESOURCE_ATTRIBUTES"] = "k=v,k2=v2"
os.environ[resources.OTEL_RESOURCE_ATTRIBUTES] = "k=v,k2=v2"
self.assertEqual(
detector.detect(), resources.Resource({"k": "v", "k2": "v2"})
)

def test_multiple_with_whitespace(self):
detector = resources.OTELResourceDetector()
os.environ["OTEL_RESOURCE_ATTRIBUTES"] = " k = v , k2 = v2 "
os.environ[
resources.OTEL_RESOURCE_ATTRIBUTES
] = " k = v , k2 = v2 "
self.assertEqual(
detector.detect(), resources.Resource({"k": "v", "k2": "v2"})
)
2 changes: 1 addition & 1 deletion opentelemetry-sdk/tests/trace/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ def test_default_span_resource(self):
tracer = tracer_provider.get_tracer(__name__)
span = tracer.start_span("root")
# pylint: disable=protected-access
self.assertIs(span.resource, resources._DEFAULT_RESOURCE)
self.assertEqual(span.resource, resources._DEFAULT_RESOURCE)

def test_span_context_remote_flag(self):
tracer = new_tracer()
Expand Down