diff --git a/CHANGELOG.md b/CHANGELOG.md index be58d61cea8..97c15396cb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v0.17b0...HEAD) +======= ### Added - Added `end_on_exit` argument to `start_as_current_span` ([#1519](https://github.com/open-telemetry/opentelemetry-python/pull/1519)]) - Add `Span.set_attributes` method to set multiple values with one call ([#1520](https://github.com/open-telemetry/opentelemetry-python/pull/1520)) +- Make sure Resources follow semantic conventions + ([#1480](https://github.com/open-telemetry/opentelemetry-python/pull/1480)) ## [0.17b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v0.17b0) - 2021-01-20 diff --git a/README.md b/README.md index 9043aeace90..c131b62fe1c 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ For information about contributing to OpenTelemetry Python, see [CONTRIBUTING.md We meet weekly on Thursdays at 9AM PST. The meeting is subject to change depending on contributors' availability. Check the [OpenTelemetry community calendar](https://calendar.google.com/calendar/embed?src=google.com_b79e3e90j7bbsa2n2p5an5lf60%40group.calendar.google.com) for specific dates. -Meetings take place via [Zoom video conference](https://zoom.us/j/6729396170). +Meetings take place via [Zoom video conference](https://zoom.us/j/6729396170). The passcode is _77777_. Meeting notes are available as a public [Google doc](https://docs.google.com/document/d/1CIMGoIOZ-c3-igzbd6_Pnxx1SjAkjwqoYSUWxPY8XIs/edit). For edit access, get in touch on [Gitter](https://gitter.im/open-telemetry/opentelemetry-python). diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index 086a2fdb5a5..29e8ff7d1e1 100644 --- 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": diff --git a/opentelemetry-sdk/tests/metrics/test_metrics.py b/opentelemetry-sdk/tests/metrics/test_metrics.py index 32c22c8c6bb..aa628655c48 100644 --- 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 index 35bffb10b8c..1121459fd00 100644 --- 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 index 71a1dcb44bd..d17bd2ef215 100644 --- 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: