Repository: open-telemetry/opentelemetry-python

Issue:
Make sure Resources follow semantic conventions
From [specs](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/resource/semantic_conventions/README.md).


Discussion:

Resolving patch:
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py
--- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py
@@ -91,9 +91,62 @@
 logger = logging.getLogger(__name__)
 
 
-TELEMETRY_SDK_LANGUAGE = "telemetry.sdk.language"
+CLOUD_PROVIDER = "cloud.provider"
+CLOUD_ACCOUNT_ID = "cloud.account.id"
+CLOUD_REGION = "cloud.region"
+CLOUD_ZONE = "cloud.zone"
+CONTAINER_NAME = "container.name"
+CONTAINER_ID = "container.id"
+CONTAINER_IMAGE_NAME = "container.image.name"
+CONTAINER_IMAGE_TAG = "container.image.tag"
+DEPLOYMENT_ENVIRONMENT = "deployment.environment"
+FAAS_NAME = "faas.name"
+FAAS_ID = "faas.id"
+FAAS_VERSION = "faas.version"
+FAAS_INSTANCE = "faas.instance"
+HOST_NAME = "host.name"
+HOST_TYPE = "host.type"
+HOST_IMAGE_NAME = "host.image.name"
+HOST_IMAGE_ID = "host.image.id"
+HOST_IMAGE_VERSION = "host.image.version"
+KUBERNETES_CLUSTER_NAME = "k8s.cluster.name"
+KUBERNETES_NAMESPACE_NAME = "k8s.namespace.name"
+KUBERNETES_POD_UID = "k8s.pod.uid"
+KUBERNETES_POD_NAME = "k8s.pod.name"
+KUBERNETES_CONTAINER_NAME = "k8s.container.name"
+KUBERNETES_REPLICA_SET_UID = "k8s.replicaset.uid"
+KUBERNETES_REPLICA_SET_NAME = "k8s.replicaset.name"
+KUBERNETES_DEPLOYMENT_UID = "k8s.deployment.uid"
+KUBERNETES_DEPLOYMENT_NAME = "k8s.deployment.name"
+KUBERNETES_STATEFUL_SET_UID = "k8s.statefulset.uid"
+KUBERNETES_STATEFUL_SET_NAME = "k8s.statefulset.name"
+KUBERNETES_DAEMON_SET_UID = "k8s.daemonset.uid"
+KUBERNETES_DAEMON_SET_NAME = "k8s.daemonset.name"
+KUBERNETES_JOB_UID = "k8s.job.uid"
+KUBERNETES_JOB_NAME = "k8s.job.name"
+KUBERNETES_CRON_JOB_UID = "k8s.cronjob.uid"
+KUBERNETES_CRON_JOB_NAME = "k8s.cronjob.name"
+OS_TYPE = "os.type"
+OS_DESCRIPTION = "os.description"
+PROCESS_PID = "process.pid"
+PROCESS_EXECUTABLE_NAME = "process.executable.name"
+PROCESS_EXECUTABLE_PATH = "process.executable.path"
+PROCESS_COMMAND = "process.command"
+PROCESS_COMMAND_LINE = "process.command_line"
+PROCESS_COMMAND_ARGS = "process.command_args"
+PROCESS_OWNER = "process.owner"
+PROCESS_RUNTIME_NAME = "process.runtime.name"
+PROCESS_RUNTIME_VERSION = "process.runtime.version"
+PROCESS_RUNTIME_DESCRIPTION = "process.runtime.description"
+SERVICE_NAME = "service.name"
+SERVICE_NAMESPACE = "service.namespace"
+SERVICE_INSTANCE_ID = "service.instance.id"
+SERVICE_VERSION = "service.version"
 TELEMETRY_SDK_NAME = "telemetry.sdk.name"
 TELEMETRY_SDK_VERSION = "telemetry.sdk.version"
+TELEMETRY_AUTO_VERSION = "telemetry.auto.version"
+TELEMETRY_SDK_LANGUAGE = "telemetry.sdk.language"
+
 
 OPENTELEMETRY_SDK_VERSION = pkg_resources.get_distribution(
     "opentelemetry-sdk"
@@ -111,7 +164,18 @@ def create(attributes: typing.Optional[Attributes] = None) -> "Resource":
             resource = _DEFAULT_RESOURCE
         else:
             resource = _DEFAULT_RESOURCE.merge(Resource(attributes))
-        return resource.merge(OTELResourceDetector().detect())
+        resource = resource.merge(OTELResourceDetector().detect())
+        if not resource.attributes.get(SERVICE_NAME, None):
+            default_service_name = "unknown_service"
+            process_executable_name = resource.attributes.get(
+                PROCESS_EXECUTABLE_NAME, None
+            )
+            if process_executable_name:
+                default_service_name += ":" + process_executable_name
+            resource = resource.merge(
+                Resource({SERVICE_NAME: default_service_name})
+            )
+        return resource
 
     @staticmethod
     def create_empty() -> "Resource":


Test patch:
diff --git a/opentelemetry-sdk/tests/metrics/test_metrics.py b/opentelemetry-sdk/tests/metrics/test_metrics.py
--- a/opentelemetry-sdk/tests/metrics/test_metrics.py
+++ b/opentelemetry-sdk/tests/metrics/test_metrics.py
@@ -30,15 +30,37 @@ def test_stateful(self):
         meter = meter_provider.get_meter(__name__)
         self.assertIs(meter.processor.stateful, False)
 
-    def test_resource(self):
-        resource = resources.Resource.create({})
+    def test_resource_empty(self):
+        resource = resources.Resource.create_empty()
         meter_provider = metrics.MeterProvider(resource=resource)
         self.assertIs(meter_provider.resource, resource)
 
-    def test_resource_empty(self):
+    def test_resources(self):
         meter_provider = metrics.MeterProvider()
         # pylint: disable=protected-access
-        self.assertEqual(meter_provider.resource, resources._DEFAULT_RESOURCE)
+        self.assertIsInstance(meter_provider.resource, resources.Resource)
+        self.assertEqual(
+            meter_provider.resource.attributes.get(resources.SERVICE_NAME),
+            "unknown_service",
+        )
+        self.assertEqual(
+            meter_provider.resource.attributes.get(
+                resources.TELEMETRY_SDK_LANGUAGE
+            ),
+            "python",
+        )
+        self.assertEqual(
+            meter_provider.resource.attributes.get(
+                resources.TELEMETRY_SDK_NAME
+            ),
+            "opentelemetry",
+        )
+        self.assertEqual(
+            meter_provider.resource.attributes.get(
+                resources.TELEMETRY_SDK_VERSION
+            ),
+            resources.OPENTELEMETRY_SDK_VERSION,
+        )
 
     def test_start_pipeline(self):
         exporter = Mock()
@@ -74,7 +96,7 @@ def test_collect_metrics(self):
         meter = metrics.MeterProvider().get_meter(__name__)
         processor_mock = Mock()
         meter.processor = processor_mock
-        counter = meter.create_counter("name", "desc", "unit", float,)
+        counter = meter.create_counter("name", "desc", "unit", float)
         labels = {"key1": "value1"}
         meter.register_view(View(counter, SumAggregator))
         counter.add(1.0, labels)
@@ -155,7 +177,7 @@ def test_create_counter(self):
         resource = Mock(spec=resources.Resource)
         meter_provider = metrics.MeterProvider(resource=resource)
         meter = meter_provider.get_meter(__name__)
-        counter = meter.create_counter("name", "desc", "unit", int,)
+        counter = meter.create_counter("name", "desc", "unit", int)
         self.assertIsInstance(counter, metrics.Counter)
         self.assertEqual(counter.value_type, int)
         self.assertEqual(counter.name, "name")
@@ -166,14 +188,14 @@ def test_instrument_same_name_error(self):
         resource = Mock(spec=resources.Resource)
         meter_provider = metrics.MeterProvider(resource=resource)
         meter = meter_provider.get_meter(__name__)
-        counter = meter.create_counter("name", "desc", "unit", int,)
+        counter = meter.create_counter("name", "desc", "unit", int)
         self.assertIsInstance(counter, metrics.Counter)
         self.assertEqual(counter.value_type, int)
         self.assertEqual(counter.name, "name")
         self.assertIs(meter_provider.resource, resource)
         self.assertEqual(counter.meter, meter)
         with self.assertRaises(ValueError) as ctx:
-            _ = meter.create_counter("naME", "desc", "unit", int,)
+            _ = meter.create_counter("naME", "desc", "unit", int)
         self.assertTrue(
             "Multiple instruments can't be registered by the same name: (name)"
             in str(ctx.exception)
@@ -182,7 +204,7 @@ def test_instrument_same_name_error(self):
     def test_create_updowncounter(self):
         meter = metrics.MeterProvider().get_meter(__name__)
         updowncounter = meter.create_updowncounter(
-            "name", "desc", "unit", float,
+            "name", "desc", "unit", float
         )
         self.assertIsInstance(updowncounter, metrics.UpDownCounter)
         self.assertEqual(updowncounter.value_type, float)
@@ -191,7 +213,7 @@ def test_create_updowncounter(self):
     def test_create_valuerecorder(self):
         meter = metrics.MeterProvider().get_meter(__name__)
         valuerecorder = meter.create_valuerecorder(
-            "name", "desc", "unit", float,
+            "name", "desc", "unit", float
         )
         self.assertIsInstance(valuerecorder, metrics.ValueRecorder)
         self.assertEqual(valuerecorder.value_type, float)
@@ -204,7 +226,7 @@ def test_register_sumobserver(self):
         callback = Mock()
 
         observer = meter.register_sumobserver(
-            callback, "name", "desc", "unit", int,
+            callback, "name", "desc", "unit", int
         )
 
         self.assertIsInstance(observer, metrics.SumObserver)
@@ -224,7 +246,7 @@ def test_register_updownsumobserver(self):
         callback = Mock()
 
         observer = meter.register_updownsumobserver(
-            callback, "name", "desc", "unit", int,
+            callback, "name", "desc", "unit", int
         )
 
         self.assertIsInstance(observer, metrics.UpDownSumObserver)
@@ -244,7 +266,7 @@ def test_register_valueobserver(self):
         callback = Mock()
 
         observer = meter.register_valueobserver(
-            callback, "name", "desc", "unit", int,
+            callback, "name", "desc", "unit", int
         )
 
         self.assertIsInstance(observer, metrics.ValueObserver)
diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py
--- a/opentelemetry-sdk/tests/resources/test_resources.py
+++ b/opentelemetry-sdk/tests/resources/test_resources.py
@@ -44,6 +44,7 @@ def test_create(self):
             resources.TELEMETRY_SDK_NAME: "opentelemetry",
             resources.TELEMETRY_SDK_LANGUAGE: "python",
             resources.TELEMETRY_SDK_VERSION: resources.OPENTELEMETRY_SDK_VERSION,
+            resources.SERVICE_NAME: "unknown_service",
         }
 
         resource = resources.Resource.create(attributes)
@@ -62,10 +63,20 @@ def test_create(self):
         self.assertEqual(resource, resources._EMPTY_RESOURCE)
 
         resource = resources.Resource.create(None)
-        self.assertEqual(resource, resources._DEFAULT_RESOURCE)
+        self.assertEqual(
+            resource,
+            resources._DEFAULT_RESOURCE.merge(
+                resources.Resource({resources.SERVICE_NAME: "unknown_service"})
+            ),
+        )
 
         resource = resources.Resource.create({})
-        self.assertEqual(resource, resources._DEFAULT_RESOURCE)
+        self.assertEqual(
+            resource,
+            resources._DEFAULT_RESOURCE.merge(
+                resources.Resource({resources.SERVICE_NAME: "unknown_service"})
+            ),
+        )
 
     def test_resource_merge(self):
         left = resources.Resource({"service": "ui"})
@@ -103,6 +114,7 @@ def test_immutability(self):
             resources.TELEMETRY_SDK_NAME: "opentelemetry",
             resources.TELEMETRY_SDK_LANGUAGE: "python",
             resources.TELEMETRY_SDK_VERSION: resources.OPENTELEMETRY_SDK_VERSION,
+            resources.SERVICE_NAME: "unknown_service",
         }
 
         attributes_copy = attributes.copy()
@@ -117,6 +129,15 @@ def test_immutability(self):
         attributes["cost"] = 999.91
         self.assertEqual(resource.attributes, attributes_copy)
 
+    def test_service_name_using_process_name(self):
+        resource = resources.Resource.create(
+            {resources.PROCESS_EXECUTABLE_NAME: "test"}
+        )
+        self.assertEqual(
+            resource.attributes.get(resources.SERVICE_NAME),
+            "unknown_service:test",
+        )
+
     def test_aggregated_resources_no_detectors(self):
         aggregated_resources = resources.get_aggregated_resources([])
         self.assertEqual(
@@ -192,7 +213,7 @@ def test_resource_detector_raise_error(self):
         resource_detector.detect.side_effect = Exception()
         resource_detector.raise_on_error = True
         self.assertRaises(
-            Exception, resources.get_aggregated_resources, [resource_detector],
+            Exception, resources.get_aggregated_resources, [resource_detector]
         )
 
 
diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py
--- a/opentelemetry-sdk/tests/trace/test_trace.py
+++ b/opentelemetry-sdk/tests/trace/test_trace.py
@@ -505,7 +505,23 @@ def test_default_span_resource(self):
         tracer = tracer_provider.get_tracer(__name__)
         span = tracer.start_span("root")
         # pylint: disable=protected-access
-        self.assertEqual(span.resource, resources._DEFAULT_RESOURCE)
+        self.assertIsInstance(span.resource, resources.Resource)
+        self.assertEqual(
+            span.resource.attributes.get(resources.SERVICE_NAME),
+            "unknown_service",
+        )
+        self.assertEqual(
+            span.resource.attributes.get(resources.TELEMETRY_SDK_LANGUAGE),
+            "python",
+        )
+        self.assertEqual(
+            span.resource.attributes.get(resources.TELEMETRY_SDK_NAME),
+            "opentelemetry",
+        )
+        self.assertEqual(
+            span.resource.attributes.get(resources.TELEMETRY_SDK_VERSION),
+            resources.OPENTELEMETRY_SDK_VERSION,
+        )
 
     def test_span_context_remote_flag(self):
         tracer = new_tracer()
@@ -835,7 +851,7 @@ def test_start_span(self):
         )
         span.set_status(new_status)
         self.assertIs(
-            span.status.status_code, trace_api.status.StatusCode.ERROR,
+            span.status.status_code, trace_api.status.StatusCode.ERROR
         )
         self.assertIs(span.status.description, "Test description")
 
@@ -930,7 +946,7 @@ def error_status_test(context):
             with self.assertRaises(AssertionError):
                 with context as root:
                     root.set_status(
-                        trace_api.status.Status(StatusCode.OK, "OK",)
+                        trace_api.status.Status(StatusCode.OK, "OK")
                     )
                     raise AssertionError("unknown")
 
@@ -987,11 +1003,9 @@ def test_record_exception_with_attributes(self):
             "RuntimeError: error",
             exception_event.attributes["exception.stacktrace"],
         )
-        self.assertIn(
-            "has_additional_attributes", exception_event.attributes,
-        )
+        self.assertIn("has_additional_attributes", exception_event.attributes)
         self.assertEqual(
-            True, exception_event.attributes["has_additional_attributes"],
+            True, exception_event.attributes["has_additional_attributes"]
         )
 
     def test_record_exception_escaped(self):
@@ -1035,9 +1049,7 @@ def test_record_exception_with_timestamp(self):
             "RuntimeError: error",
             exception_event.attributes["exception.stacktrace"],
         )
-        self.assertEqual(
-            1604238587112021089, exception_event.timestamp,
-        )
+        self.assertEqual(1604238587112021089, exception_event.timestamp)
 
     def test_record_exception_with_attributes_and_timestamp(self):
         span = trace._Span("name", mock.Mock(spec=trace_api.SpanContext))
@@ -1059,15 +1071,11 @@ def test_record_exception_with_attributes_and_timestamp(self):
             "RuntimeError: error",
             exception_event.attributes["exception.stacktrace"],
         )
-        self.assertIn(
-            "has_additional_attributes", exception_event.attributes,
-        )
-        self.assertEqual(
-            True, exception_event.attributes["has_additional_attributes"],
-        )
+        self.assertIn("has_additional_attributes", exception_event.attributes)
         self.assertEqual(
-            1604238587112021089, exception_event.timestamp,
+            True, exception_event.attributes["has_additional_attributes"]
         )
+        self.assertEqual(1604238587112021089, exception_event.timestamp)
 
     def test_record_exception_context_manager(self):
         try: