Skip to content

Commit

Permalink
Merge OTELResourceDetector result when creating resources (open-telem…
Browse files Browse the repository at this point in the history
  • Loading branch information
aabmass authored and Michael Stella committed Sep 25, 2020
1 parent f1199ff commit ef00b2e
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 16 deletions.
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

0 comments on commit ef00b2e

Please sign in to comment.