From ef00b2e501ddf978ba9705910930bd775a6ee16a Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Fri, 18 Sep 2020 18:33:24 -0400 Subject: [PATCH] Merge OTELResourceDetector result when creating resources (#1096) --- opentelemetry-sdk/CHANGELOG.md | 2 + .../opentelemetry/sdk/resources/__init__.py | 76 ++++++++++++++++++- .../tests/metrics/test_metrics.py | 2 +- .../tests/resources/test_resources.py | 36 ++++++--- opentelemetry-sdk/tests/trace/test_trace.py | 2 +- 5 files changed, 102 insertions(+), 16 deletions(-) diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index efa139f347b..7fd47150ca9 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -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 diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index e14d7811684..086a2fdb5a5 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -12,6 +12,71 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +This package implements `OpenTelemetry Resources +`_: + + *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" +`_ +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 +`_. + +.. code-block:: console + + $ OTEL_RESOURCE_ATTRIBUTES="service.name=shoppingcard,will_be_overridden=foo" python - < "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": @@ -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 = { diff --git a/opentelemetry-sdk/tests/metrics/test_metrics.py b/opentelemetry-sdk/tests/metrics/test_metrics.py index 01974765203..8e412f3c5cb 100644 --- a/opentelemetry-sdk/tests/metrics/test_metrics.py +++ b/opentelemetry-sdk/tests/metrics/test_metrics.py @@ -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() diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index 3166e3350ee..35bffb10b8c 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -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", @@ -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"}) @@ -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"}) ) diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index fabbc62d2e8..fdf85ef19b5 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -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()