From ca9f91b3391fc7cdb209a7dda5bbe00698053c7f Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Fri, 11 Sep 2020 01:32:05 +0000 Subject: [PATCH 1/2] merge OTELResourceDetector result --- opentelemetry-sdk/CHANGELOG.md | 2 ++ .../opentelemetry/sdk/resources/__init__.py | 11 +++--- .../tests/metrics/test_metrics.py | 2 +- .../tests/resources/test_resources.py | 36 +++++++++++++------ opentelemetry-sdk/tests/trace/test_trace.py | 2 +- 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index e31387a0c40..0cf39815e58 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -14,6 +14,8 @@ ([#1053](https://github.com/open-telemetry/opentelemetry-python/pull/1053)) - Rename Resource labels to attributes ([#1082](https://github.com/open-telemetry/opentelemetry-python/pull/1082)) +- 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..d70de357691 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -33,6 +33,7 @@ OPENTELEMETRY_SDK_VERSION = pkg_resources.get_distribution( "opentelemetry-sdk" ).version +OTEL_RESOURCE_ATTRIBUTES = "OTEL_RESOURCE_ATTRIBUTES" class Resource: @@ -40,10 +41,12 @@ def __init__(self, attributes: Attributes): self._attributes = attributes.copy() @staticmethod - def create(attributes: Attributes) -> "Resource": + def create(attributes: typing.Optional[Attributes]) -> "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 +95,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 a6b4fa93e99..f33e7ff7793 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() From d79e7898b283598c38daaf0a018b474746231a4f Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Tue, 15 Sep 2020 16:26:45 +0000 Subject: [PATCH 2/2] added documentation for Resources and OTEL_RESOURCE_ATTRIBUTES --- .../opentelemetry/sdk/resources/__init__.py | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index d70de357691..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: resource = _DEFAULT_RESOURCE else: