From 13df782cd2cf729948e916d33f09e60b88d61440 Mon Sep 17 00:00:00 2001 From: Michael Aaron Safyan Date: Wed, 12 Feb 2025 13:51:03 -0500 Subject: [PATCH 01/13] Begin work on a GCP configurator. --- opentelemetry-configurator-gcp/Makefile | 0 opentelemetry-configurator-gcp/README.rst | 3 +++ opentelemetry-configurator-gcp/pyproject.toml | 9 +++++++++ .../src/opentelemetry/configurator/gcp/__init__.py | 0 .../src/opentelemetry/configurator/gcp/configurator.py | 0 .../src/opentelemetry/configurator/gcp/logs.py | 0 .../src/opentelemetry/configurator/gcp/metrics.py | 0 .../src/opentelemetry/configurator/gcp/resource.py | 0 .../src/opentelemetry/configurator/gcp/traces.py | 0 9 files changed, 12 insertions(+) create mode 100644 opentelemetry-configurator-gcp/Makefile create mode 100644 opentelemetry-configurator-gcp/README.rst create mode 100644 opentelemetry-configurator-gcp/pyproject.toml create mode 100644 opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py create mode 100644 opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py create mode 100644 opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py create mode 100644 opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py create mode 100644 opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py create mode 100644 opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py diff --git a/opentelemetry-configurator-gcp/Makefile b/opentelemetry-configurator-gcp/Makefile new file mode 100644 index 00000000..e69de29b diff --git a/opentelemetry-configurator-gcp/README.rst b/opentelemetry-configurator-gcp/README.rst new file mode 100644 index 00000000..25fb96e6 --- /dev/null +++ b/opentelemetry-configurator-gcp/README.rst @@ -0,0 +1,3 @@ +OpenTelemetry Google Cloud Configurator +======================================== + diff --git a/opentelemetry-configurator-gcp/pyproject.toml b/opentelemetry-configurator-gcp/pyproject.toml new file mode 100644 index 00000000..40154f37 --- /dev/null +++ b/opentelemetry-configurator-gcp/pyproject.toml @@ -0,0 +1,9 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "opentelemetry-configurator-gcp" + +[project.entry-points.opentelemetry_configurator] +gcp = "opentelemetry.configurator.gcp:GcpConfigurator" diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py new file mode 100644 index 00000000..e69de29b diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py new file mode 100644 index 00000000..e69de29b diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py new file mode 100644 index 00000000..e69de29b diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py new file mode 100644 index 00000000..e69de29b diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py new file mode 100644 index 00000000..e69de29b From 1378c9c93041f15f29eed04ff5fbba2b1dba3678 Mon Sep 17 00:00:00 2001 From: Michael Aaron Safyan Date: Wed, 12 Feb 2025 14:15:46 -0500 Subject: [PATCH 02/13] Snapshot progress thus far. --- .../configurator/gcp/__init__.py | 5 +++ .../configurator/gcp/configurator.py | 44 +++++++++++++++++++ .../opentelemetry/configurator/gcp/flags.py | 0 3 files changed, 49 insertions(+) create mode 100644 opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py index e69de29b..1d078ed1 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py @@ -0,0 +1,5 @@ +from .configurator import OpenTelemetryGcpConfigurator + +__all__ = [ + "OpenTelemetryGcpConfigurator" +] diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py index e69de29b..736028f8 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py @@ -0,0 +1,44 @@ +from .flags import ( + is_metrics_exporter_enabled, + is_logs_exporter_enabled, + is_traces_exporter_enabled, + is_resource_detector_enabled, +) +from .resource import get_resource +from .logs import configure_logs_exporter +from .metrics import configure_metrics_exporter +from .traces import configure_traces_exporter + + +def _bool_with_flag_default(value: Optional[bool], flag_lookup: Callable[None, bool]): + if value is not None: + return value + return flag_lookup() + + +class OpenTelemetryGcpConfigurator: + + def __init__( + self, + metrics_exporter_enabled:Optional[bool]=None, + logs_exporter_enabled:Optional[bool]=None, + traces_exporter_enabled:Optional[bool]=None, + resource_detector_enabled:Optional[bool]=None): + self._metrics_exporter_enabled = _bool_with_flag_default( + metrics_exporter_enabled, is_metrics_exporter_enabled) + self._logs_exporter_enabled = _bool_with_flag_default( + logs_exporter_enabled, is_logs_exporter_enabled) + self._traces_exporter_enabled = _bool_with_flag_default( + traces_exporter_enabled, is_traces_exporter_enabled) + self._resource_detector_enabled = _bool_with_flag_default( + resource_detector_enabled, is_resource_detector_enabled) + + def configure(self): + resource = get_resource(include_gcp_detector=self._resource_detector_enabled) + if self._metrics_exporter_enabled: + configure_metrics_exporter(resource=resource) + if self._logs_exporter_enabled: + configure_logs_exporter(resource=resource) + if self._traces_exporter_enabled: + configure_traces_exporter(resource=resource) + diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py new file mode 100644 index 00000000..e69de29b From 8b9c3dd27a9b3b8294b655811ffa7abb1fd5aa6f Mon Sep 17 00:00:00 2001 From: Michael Aaron Safyan Date: Thu, 13 Feb 2025 15:40:31 -0500 Subject: [PATCH 03/13] Snapshot current progress. --- .../opentelemetry/configurator/gcp/flags.py | 49 +++++++++++++++ .../configurator/gcp/gcloud_env.py | 63 +++++++++++++++++++ .../opentelemetry/configurator/gcp/logs.py | 4 ++ .../opentelemetry/configurator/gcp/metrics.py | 5 ++ .../configurator/gcp/resource.py | 4 ++ .../opentelemetry/configurator/gcp/traces.py | 3 + 6 files changed, 128 insertions(+) create mode 100644 opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/gcloud_env.py diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py index e69de29b..6d663648 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py @@ -0,0 +1,49 @@ +import gcloud_env + + +def _str_to_optional_bool(s): + lower_s = s.lower() + if lower_s in ['1', 'true', 't', 'y', 'yes', 'on']: + return True + if lower_s in ['0', 'false', 'f', 'n', 'no', 'off']: + return False + return None + + +def _get_bool_flag_from_env(env_var_name, default_value=None): + s = os.getenv(env_var_name) + if s is None: + return default_value + result = _str_to_optional_bool(s) + if result is not None: + return result + return default_value + + +def is_metrics_exporter_enabled(): + return _get_bool_flag_from_env( + 'OTEL_GCP_METRICS_EXPORTER_ENABLED', + default_value=True + ) + + +def is_logs_exporter_enabled(): + return _get_bool_flag_from_env( + 'OTEL_GCP_LOGS_EXPORTER_ENABLED', + default_value=True + ) + + +def is_traces_exporter_enabled(): + return _get_bool_flag_from_env( + 'OTEL_GCP_TRACES_EXPORTER_ENABLED', + default_value=True + ) + + +def is_resource_detector_enabled(): + result = _get_bool_flag_from_env( + 'OTEL_GCP_RESOURCE_DETECTOR_ENABLED') + if result is not None: + return result + return gcloud_env.is_running_on_gcp() diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/gcloud_env.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/gcloud_env.py new file mode 100644 index 00000000..46453d16 --- /dev/null +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/gcloud_env.py @@ -0,0 +1,63 @@ +import os +import os.path +import socket + + +def _can_resolve_metadata_server(): + try: + socket.getaddrinfo('metadata.google.internal', 80) + return True + except: + return False + + +def _is_likely_gae(): + return (('GAE_APPLICATION' in os.environ) and + ('GAE_DEPLOYMENT_ID' in os.environ) and + ('GAE_SERVICE' in os.environ)) + + +def _is_likely_cloud_run(): + return ( + ('K_SERVICE' in os.environ) and + ('K_REVISION' in os.environ) and + ('K_CONFIGURATION' in os.environ)) + + +def _is_likely_gce(): + return os.path.exists('/run/google-mds-mtls') + + +def _is_likely_gke(): + return ('KUBERNETES_SERVICE_HOST' in os.environ) + + +_NOT_GOOGLE_CLOUD = 'NOT_GCLOUD' +_GAE = 'GOOGLE_APP_ENGINE' +_CLOUD_RUN = 'CLOUD_RUN' +_GKE = 'GKE' +_GCE = 'GCE' + +_detected_environ = None + +def _detect_environ(): + global _detected_environ + if _detected_environ is not None: + return _detected_environ + if not _can_resolve_metadata_server(): + _detected_environ = _NOT_GOOGLE_CLOUD + elif _is_likely_gae(): + _detected_environ = _GAE + elif _is_likely_gke(): + _detected_environ = _GKE + elif _is_likely_cloud_run(): + _detected_environ = _CLOUD_RUN + elif _is_likely_gce(): + _detected_environ = _GCE + else: + _detected_environ = _NOT_GOOGLE_CLOUD + return _detected_environ + + +def is_running_on_gcp(): + return _detect_environ() != _NOT_GOOGLE_CLOUD diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py index e69de29b..c462fa33 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py @@ -0,0 +1,4 @@ + +def configure_logs_exporter(resource=None): + # TODO:... + pass \ No newline at end of file diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py index e69de29b..19096ceb 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py @@ -0,0 +1,5 @@ + + +def configure_metrics_exporter(resource=None): + # TODO:... + pass diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py index e69de29b..43f7a513 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py @@ -0,0 +1,4 @@ + +def get_resource(include_gcp_detector=False): + # TODO: ... + pass \ No newline at end of file diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py index e69de29b..19aac67a 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py @@ -0,0 +1,3 @@ +def configure_traces_exporter(resource=None): + # TODO: ... + pass \ No newline at end of file From 10dd68400847455ff42e8c1a2b2fa8274f6c901f Mon Sep 17 00:00:00 2001 From: Michael Aaron Safyan Date: Thu, 13 Feb 2025 16:24:48 -0500 Subject: [PATCH 04/13] Implement 'configure_traces_exporter'. --- .../src/opentelemetry/configurator/gcp/traces.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py index 19aac67a..9199cdaa 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py @@ -1,3 +1,10 @@ +from opentelemetry import trace +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter +from opentelemetry.sdk.trace.export import BatchSpanProcessor + + def configure_traces_exporter(resource=None): - # TODO: ... - pass \ No newline at end of file + provider = TracerProvider() + provider.add_span_processor(BatchSpanProcessor(CloudTraceSpanExporter())) + trace.set_tracer_provider(provider) From ab1da4e27fadd960b4c97ecae6a8cab4de4e657e Mon Sep 17 00:00:00 2001 From: Michael Aaron Safyan Date: Thu, 13 Feb 2025 16:26:22 -0500 Subject: [PATCH 05/13] Propagate resource to the TracerProvider. --- .../src/opentelemetry/configurator/gcp/traces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py index 9199cdaa..fad08bdc 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py @@ -5,6 +5,6 @@ def configure_traces_exporter(resource=None): - provider = TracerProvider() + provider = TracerProvider(resource=resource) provider.add_span_processor(BatchSpanProcessor(CloudTraceSpanExporter())) trace.set_tracer_provider(provider) From 4124b9b25ffc380acf5b3f3874fed97e7af6cb32 Mon Sep 17 00:00:00 2001 From: Michael Aaron Safyan Date: Thu, 13 Feb 2025 16:29:27 -0500 Subject: [PATCH 06/13] Implement metrics export. --- .../src/opentelemetry/configurator/gcp/metrics.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py index 19096ceb..b249b5f2 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py @@ -1,5 +1,15 @@ +from opentelemetry import metrics as otel_metrics +from opentelemetry.sdk import metrics as otel_metrics_sdk +from opentelemetry.sdk.metrics import export as otel_metrics_sdk_export +from opentelemetry.exporter import cloud_monitoring as otel_cloud_monitoring def configure_metrics_exporter(resource=None): - # TODO:... - pass + provider = otel_metrics_sdk.MeterProvider( + metric_readers=[ + otel_metrics_sdk_export.PeriodicExportingMetricReader( + otel_cloud_monitoring.CloudMonitoringMetricsExporter() + ), + ], + resource=resource) + otel_metrics.set_meter_provider(provider) From 6b0f4b5b5dd90460044a0ec5dc8555b2a89310b0 Mon Sep 17 00:00:00 2001 From: Michael Aaron Safyan Date: Thu, 13 Feb 2025 22:00:13 -0500 Subject: [PATCH 07/13] Add support for logs and resources. --- .../configurator/gcp/gcloud_env.py | 38 ++++--------- .../opentelemetry/configurator/gcp/logs.py | 53 ++++++++++++++++++- .../configurator/gcp/resource.py | 13 ++++- 3 files changed, 72 insertions(+), 32 deletions(-) diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/gcloud_env.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/gcloud_env.py index 46453d16..723d9251 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/gcloud_env.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/gcloud_env.py @@ -32,32 +32,14 @@ def _is_likely_gke(): return ('KUBERNETES_SERVICE_HOST' in os.environ) -_NOT_GOOGLE_CLOUD = 'NOT_GCLOUD' -_GAE = 'GOOGLE_APP_ENGINE' -_CLOUD_RUN = 'CLOUD_RUN' -_GKE = 'GKE' -_GCE = 'GCE' - -_detected_environ = None - -def _detect_environ(): - global _detected_environ - if _detected_environ is not None: - return _detected_environ - if not _can_resolve_metadata_server(): - _detected_environ = _NOT_GOOGLE_CLOUD - elif _is_likely_gae(): - _detected_environ = _GAE - elif _is_likely_gke(): - _detected_environ = _GKE - elif _is_likely_cloud_run(): - _detected_environ = _CLOUD_RUN - elif _is_likely_gce(): - _detected_environ = _GCE - else: - _detected_environ = _NOT_GOOGLE_CLOUD - return _detected_environ - - def is_running_on_gcp(): - return _detect_environ() != _NOT_GOOGLE_CLOUD + return ( + _can_resolve_metadata_server() and + ( + _is_likely_gke() or + _is_likely_cloud_run() or + _is_likely_gce() or + _is_likely_gae() + ) + ) + diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py index c462fa33..5aef1ef8 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py @@ -1,4 +1,53 @@ +import sys +import os +import os.path +import logging +from opentelemetry.exporter.cloud_logging import ( + CloudLoggingExporter, +) +from opentelemetry._logs import set_logger_provider +from opentelemetry.sdk import environment_variables as otel_env_vars +from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler +from opentelemetry.sdk._logs.export import BatchLogRecordProcessor + + +_LEVEL_NAME_TO_LEVEL = { + 'info': logging.INFO, + 'error': logging.ERROR, + 'debug': logging.DEBUG, + 'warning': logging.WARNING, +} + + +def _get_entrypoint_script_name(): + main_script_path = sys.argv[0] + if not main_script_path: + main_script_path = sys.executable + simple_script_name = os.path.basename(main_scripot_path).rstrip('.py') + return simple_script_name + + +def _get_log_name(): + log_name = os.getenv('OTEL_GCP_LOG_NAME') + if log_name: + return log_name + return _get_entrypoint_script_name() + + +def _get_log_level(): + level = os.getenv(otel_env_vars.OTEL_LOG_LEVEL) + if level is None: + return logging.INFO + level_value = _LEVEL_NAME_TO_LEVEL.get(level) + if level_value is None: + return logging.INFO + return level_value + def configure_logs_exporter(resource=None): - # TODO:... - pass \ No newline at end of file + provider = LoggerProvider(resource=resource) + provider.add_log_record_processor(BatchLogRecordProcessor( + CloudLoggingExporter(default_log_name=_get_log_name()))) + set_logger_provider(provider) + handler = LoggingHandler(level=_get_log_level(), logger_provider=provider) + logging.getLogger().addHandler(handler) diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py index 43f7a513..a9790294 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py @@ -1,4 +1,13 @@ +from opentelemetry.sdk import resources as otel_resources_sdk +from opentelemetry.resourcedetector import gcp_resource_detector + def get_resource(include_gcp_detector=False): - # TODO: ... - pass \ No newline at end of file + detectors = [ + otel_resources_sdk.OTELResourceDetector(), + otel_resources_sdk.ProcessResourceDetector(), + otel_resources_sdk.OsResourceDetector(), + ] + if include_gcp_detector: + detectors.append(gcp_resource_detector.GoogleCloudResourceDetector()) + return otel_resources_sdk.get_aggregated_resources(detectors) From de56cd3c3d557c643e65d4b59241b6cf299341cf Mon Sep 17 00:00:00 2001 From: Michael Aaron Safyan Date: Thu, 13 Feb 2025 22:18:12 -0500 Subject: [PATCH 08/13] Add a version. --- .../src/opentelemetry/configurator/gcp/__init__.py | 4 +++- .../src/opentelemetry/configurator/gcp/version.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/version.py diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py index 1d078ed1..561c7fb6 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py @@ -1,5 +1,7 @@ from .configurator import OpenTelemetryGcpConfigurator +from .version import __version__ __all__ = [ - "OpenTelemetryGcpConfigurator" + "OpenTelemetryGcpConfigurator", + "__version__", ] diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/version.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/version.py new file mode 100644 index 00000000..15addcbc --- /dev/null +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/version.py @@ -0,0 +1 @@ +__version__ = "0.0.1.dev0" From 49b239b2cdec9c9ed88f8e366235579512443d2e Mon Sep 17 00:00:00 2001 From: Michael Aaron Safyan Date: Thu, 13 Feb 2025 23:18:55 -0500 Subject: [PATCH 09/13] Added first test. --- opentelemetry-configurator-gcp/Makefile | 22 ++++++++++++++++++ opentelemetry-configurator-gcp/pyproject.toml | 18 +++++++++++++++ .../configurator/gcp/configurator.py | 18 ++++++++------- .../opentelemetry/configurator/gcp/flags.py | 4 +++- .../opentelemetry/configurator/gcp/logs.py | 2 +- .../tests/requirements.txt | 6 +++++ .../tests/run_with_env.sh | 21 +++++++++++++++++ .../tests/test_manual_default_parameters.py | 16 +++++++++++++ opentelemetry-configurator-gcp/tools/build.sh | 23 +++++++++++++++++++ opentelemetry-configurator-gcp/tools/lint.sh | 0 opentelemetry-configurator-gcp/tools/test.sh | 0 11 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 opentelemetry-configurator-gcp/tests/requirements.txt create mode 100755 opentelemetry-configurator-gcp/tests/run_with_env.sh create mode 100755 opentelemetry-configurator-gcp/tests/test_manual_default_parameters.py create mode 100755 opentelemetry-configurator-gcp/tools/build.sh create mode 100755 opentelemetry-configurator-gcp/tools/lint.sh create mode 100755 opentelemetry-configurator-gcp/tools/test.sh diff --git a/opentelemetry-configurator-gcp/Makefile b/opentelemetry-configurator-gcp/Makefile index e69de29b..748801b1 100644 --- a/opentelemetry-configurator-gcp/Makefile +++ b/opentelemetry-configurator-gcp/Makefile @@ -0,0 +1,22 @@ +.PHONY: all build test clean lint install + +all: build test lint + +build: + ./tools/build.sh + +test: + ./tools/test.sh + +lint: + ./tools/lint.sh + +install: build + pip install dist/*.whl + +clean: + rm -rf .build + rm -rf dist + rm -rf .test + rm -rf .tox + rm -rf .venv diff --git a/opentelemetry-configurator-gcp/pyproject.toml b/opentelemetry-configurator-gcp/pyproject.toml index 40154f37..b47f5c61 100644 --- a/opentelemetry-configurator-gcp/pyproject.toml +++ b/opentelemetry-configurator-gcp/pyproject.toml @@ -4,6 +4,24 @@ build-backend = "hatchling.build" [project] name = "opentelemetry-configurator-gcp" +dynamic = ["version"] +dependencies = [ + "opentelemetry-api >= 1.30.0, <2", + "opentelemetry-sdk >= 1.30.0, <2", + "opentelemetry-exporter-gcp-logging >= 1.9.0a0, <2", + "opentelemetry-exporter-gcp-trace >= 1.9.0, <2", + "opentelemetry-exporter-gcp-monitoring >= 1.9.0a0, <2", + "opentelemetry-resourcedetector-gcp >= 1.9.0a0, <2", +] [project.entry-points.opentelemetry_configurator] gcp = "opentelemetry.configurator.gcp:GcpConfigurator" + +[tool.hatch.build.targets.sdist] +include = ["*.py"] + +[tool.hatch.build.targets.wheel] +packages = ["src/opentelemetry"] + +[tool.hatch.version] +path = "src/opentelemetry/configurator/gcp/version.py" diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py index 736028f8..4bca0f6f 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py @@ -1,3 +1,5 @@ +from typing import Optional, Callable + from .flags import ( is_metrics_exporter_enabled, is_logs_exporter_enabled, @@ -24,14 +26,14 @@ def __init__( logs_exporter_enabled:Optional[bool]=None, traces_exporter_enabled:Optional[bool]=None, resource_detector_enabled:Optional[bool]=None): - self._metrics_exporter_enabled = _bool_with_flag_default( - metrics_exporter_enabled, is_metrics_exporter_enabled) - self._logs_exporter_enabled = _bool_with_flag_default( - logs_exporter_enabled, is_logs_exporter_enabled) - self._traces_exporter_enabled = _bool_with_flag_default( - traces_exporter_enabled, is_traces_exporter_enabled) - self._resource_detector_enabled = _bool_with_flag_default( - resource_detector_enabled, is_resource_detector_enabled) + self._metrics_exporter_enabled = _bool_with_flag_default( + metrics_exporter_enabled, is_metrics_exporter_enabled) + self._logs_exporter_enabled = _bool_with_flag_default( + logs_exporter_enabled, is_logs_exporter_enabled) + self._traces_exporter_enabled = _bool_with_flag_default( + traces_exporter_enabled, is_traces_exporter_enabled) + self._resource_detector_enabled = _bool_with_flag_default( + resource_detector_enabled, is_resource_detector_enabled) def configure(self): resource = get_resource(include_gcp_detector=self._resource_detector_enabled) diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py index 6d663648..c0c0727f 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py @@ -1,4 +1,6 @@ -import gcloud_env +import os + +from . import gcloud_env def _str_to_optional_bool(s): diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py index 5aef1ef8..a5479c61 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py @@ -23,7 +23,7 @@ def _get_entrypoint_script_name(): main_script_path = sys.argv[0] if not main_script_path: main_script_path = sys.executable - simple_script_name = os.path.basename(main_scripot_path).rstrip('.py') + simple_script_name = os.path.basename(main_script_path).rstrip('.py') return simple_script_name diff --git a/opentelemetry-configurator-gcp/tests/requirements.txt b/opentelemetry-configurator-gcp/tests/requirements.txt new file mode 100644 index 00000000..5cab59a0 --- /dev/null +++ b/opentelemetry-configurator-gcp/tests/requirements.txt @@ -0,0 +1,6 @@ +opentelemetry-api==1.30.0 +opentelemetry-sdk==1.30.0 +opentelemetry-exporter-gcp-logging==1.9.0a0 +opentelemetry-exporter-gcp-trace==1.9.0 +opentelemetry-exporter-gcp-monitoring==1.9.0a0 +opentelemetry-resourcedetector-gcp==1.9.0a0 diff --git a/opentelemetry-configurator-gcp/tests/run_with_env.sh b/opentelemetry-configurator-gcp/tests/run_with_env.sh new file mode 100755 index 00000000..435a2daa --- /dev/null +++ b/opentelemetry-configurator-gcp/tests/run_with_env.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +SCRIPT_DIR=$(cd $(dirname ${BASH_SOURCE:-0}); pwd) +PROJECT_DIR=$(readlink -f "${SCRIPT_DIR}/..") +TESTS_DIR="${PROJECT_DIR}/tests" +TESTS_ENV="${PROJECT_DIR}/.test/.test-venv" + +function main() { + if [ ! -d "${TESTS_ENV}" ] ; then + mkdir -p "${TESTS_ENV}" + fi + if [ ! -d "${TESTS_ENV}/bin" ] ; then + python3 -m venv "${TESTS_ENV}" + fi + + source "${TESTS_ENV}/bin/activate" + pip install -r "${TESTS_DIR}/requirements.txt" + python3 "$@" +} + +main "$@" diff --git a/opentelemetry-configurator-gcp/tests/test_manual_default_parameters.py b/opentelemetry-configurator-gcp/tests/test_manual_default_parameters.py new file mode 100755 index 00000000..57dd67b3 --- /dev/null +++ b/opentelemetry-configurator-gcp/tests/test_manual_default_parameters.py @@ -0,0 +1,16 @@ +#!./run_with_env.sh +import unittest + +import sys +sys.path.append('../src') + +from opentelemetry.configurator.gcp import OpenTelemetryGcpConfigurator + +class ManualTestWithDefaultParameters(unittest.TestCase): + + def test_does_not_crash(self): + OpenTelemetryGcpConfigurator().configure() + + +if __name__ == '__main__': + unittest.main() diff --git a/opentelemetry-configurator-gcp/tools/build.sh b/opentelemetry-configurator-gcp/tools/build.sh new file mode 100755 index 00000000..9e0a55ad --- /dev/null +++ b/opentelemetry-configurator-gcp/tools/build.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +SCRIPT_DIR=$(cd $(dirname ${BASH_SOURCE:-0}); pwd) +PROJECT_DIR=$(readlink -f "${SCRIPT_DIR}/..") +BUILD_DIR="${PROJECT_DIR}/.build" +BUILD_ENV="${BUILD_DIR}/.venv" + +function main() { + if [ ! -d "${BUILD_ENV}" ] ; then + mkdir -p "${BUILD_ENV}" + fi + if [ ! -d "${BUILD_ENV}/bin" ] ; then + python3 -m venv "${BUILD_ENV}" + fi + + source "${BUILD_ENV}/bin/activate" + pip install build + + cd "${PROJECT_DIR}" + python3 -m build +} + +main diff --git a/opentelemetry-configurator-gcp/tools/lint.sh b/opentelemetry-configurator-gcp/tools/lint.sh new file mode 100755 index 00000000..e69de29b diff --git a/opentelemetry-configurator-gcp/tools/test.sh b/opentelemetry-configurator-gcp/tools/test.sh new file mode 100755 index 00000000..e69de29b From 97c0dacf8954c150b1424e147d31753c2e320dfd Mon Sep 17 00:00:00 2001 From: Michael Aaron Safyan Date: Fri, 14 Feb 2025 00:15:00 -0500 Subject: [PATCH 10/13] Added tests, including for auto-instrumentation. --- opentelemetry-configurator-gcp/pyproject.toml | 2 +- .../configurator/gcp/configurator.py | 2 +- .../tests/run_with_autoinstrumentation.py | 47 +++++++++++++++++++ .../tests/test_automatic.py | 13 +++++ .../tests/test_automatic.sh | 32 +++++++++++++ .../tests/test_manual_all_enabled.py | 22 +++++++++ .../tests/test_manual_logging_only.py | 21 +++++++++ .../tests/test_manual_metrics_only.py | 21 +++++++++ .../tests/test_manual_resource_off.py | 19 ++++++++ .../tests/test_manual_resource_on.py | 19 ++++++++ .../tests/test_manual_tracing_only.py | 21 +++++++++ opentelemetry-configurator-gcp/tools/build.sh | 14 +++--- opentelemetry-configurator-gcp/tools/test.sh | 27 +++++++++++ 13 files changed, 252 insertions(+), 8 deletions(-) create mode 100755 opentelemetry-configurator-gcp/tests/run_with_autoinstrumentation.py create mode 100755 opentelemetry-configurator-gcp/tests/test_automatic.py create mode 100755 opentelemetry-configurator-gcp/tests/test_automatic.sh create mode 100755 opentelemetry-configurator-gcp/tests/test_manual_all_enabled.py create mode 100755 opentelemetry-configurator-gcp/tests/test_manual_logging_only.py create mode 100755 opentelemetry-configurator-gcp/tests/test_manual_metrics_only.py create mode 100755 opentelemetry-configurator-gcp/tests/test_manual_resource_off.py create mode 100755 opentelemetry-configurator-gcp/tests/test_manual_resource_on.py create mode 100755 opentelemetry-configurator-gcp/tests/test_manual_tracing_only.py diff --git a/opentelemetry-configurator-gcp/pyproject.toml b/opentelemetry-configurator-gcp/pyproject.toml index b47f5c61..c75249fe 100644 --- a/opentelemetry-configurator-gcp/pyproject.toml +++ b/opentelemetry-configurator-gcp/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ ] [project.entry-points.opentelemetry_configurator] -gcp = "opentelemetry.configurator.gcp:GcpConfigurator" +gcp = "opentelemetry.configurator.gcp:OpenTelemetryGcpConfigurator" [tool.hatch.build.targets.sdist] include = ["*.py"] diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py index 4bca0f6f..ed644103 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py @@ -35,7 +35,7 @@ def __init__( self._resource_detector_enabled = _bool_with_flag_default( resource_detector_enabled, is_resource_detector_enabled) - def configure(self): + def configure(self, **kwargs): resource = get_resource(include_gcp_detector=self._resource_detector_enabled) if self._metrics_exporter_enabled: configure_metrics_exporter(resource=resource) diff --git a/opentelemetry-configurator-gcp/tests/run_with_autoinstrumentation.py b/opentelemetry-configurator-gcp/tests/run_with_autoinstrumentation.py new file mode 100755 index 00000000..0f5cb039 --- /dev/null +++ b/opentelemetry-configurator-gcp/tests/run_with_autoinstrumentation.py @@ -0,0 +1,47 @@ +#! /usr/bin/env python3 + +import sys + +from opentelemetry import trace as otel_trace +from opentelemetry import metrics as otel_metrics +from opentelemetry import _logs as otel_logs + + +def is_tracer_noop(): + tracer = otel_trace.get_tracer(__name__) + if isinstance(tracer, otel_trace.ProxyTracer): + tracer = getattr(tracer, '_tracer') + return isinstance(tracer, otel_trace.NoOpTracer) + + +def is_metrics_noop(): + return isinstance(otel_metrics.get_meter_provider(), otel_metrics.NoOpMeterProvider) + + +def is_logging_noop(): + return isinstance(otel_logs.get_logger_provider(), otel_logs.NoOpLoggerProvider) + + +def main(): + signals_to_noop_checker = { + 'trace': is_tracer_noop, + 'metrics': is_metrics_noop, + 'logs': is_logging_noop, + } + noop_signals = [] + for signal_name, noop_tester_func in signals_to_noop_checker.items(): + is_noop = noop_tester_func() + print(f'Signal "{signal_name}" is no-op?: {is_noop}') + if is_noop: + noop_signals.append(signal_name) + if not noop_signals: + print('All signals successfully configured.') + return + noop_count = len(noop_signals) + total_count = len(signals_to_noop_checker) + print(f'{noop_count}/{total_count} signals not configured: {noop_signals}') + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/opentelemetry-configurator-gcp/tests/test_automatic.py b/opentelemetry-configurator-gcp/tests/test_automatic.py new file mode 100755 index 00000000..14d934c9 --- /dev/null +++ b/opentelemetry-configurator-gcp/tests/test_automatic.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +import subprocess +import unittest + +class AutomaticInstrumentationTestCase(unittest.TestCase): + + def test_works_with_auto_instrumentation(self): + subprocess.run("./test_automatic.sh", shell=True, check=True, capture_output=True) + + +if __name__ == '__main__': + unittest.main() diff --git a/opentelemetry-configurator-gcp/tests/test_automatic.sh b/opentelemetry-configurator-gcp/tests/test_automatic.sh new file mode 100755 index 00000000..46aa362c --- /dev/null +++ b/opentelemetry-configurator-gcp/tests/test_automatic.sh @@ -0,0 +1,32 @@ +#! /bin/bash + +set -o pipefail + +SCRIPT_DIR=$(cd $(dirname ${BASH_SOURCE:-0}); pwd) +PROJECT_DIR=$(readlink -f "${SCRIPT_DIR}/..") +TESTS_DIR="${PROJECT_DIR}/tests" +TEST_ENV="${PROJECT_DIR}/.test/.venv-auto" + +function main() { + if [ ! -d "${TEST_ENV}" ] ; then + mkdir -p "${TEST_ENV}" || exit 1 + fi + if [ ! -d "${TEST_ENV}/bin" ] ; then + python3 -m venv "${TEST_ENV}" || exit 1 + fi + + source "${TEST_ENV}/bin/activate" || exit 1 + pip install -r "${TESTS_DIR}/requirements.txt" || exit 1 + + cd "${PROJECT_DIR}" || exit 1 + make install || exit 1 + pip install opentelemetry-instrumentation || exit 1 + pip install opentelemetry-distro || exit 1 + + opentelemetry-instrument \ + --configurator=gcp \ + python \ + "${TESTS_DIR}/run_with_autoinstrumentation.py" || exit $? +} + +main diff --git a/opentelemetry-configurator-gcp/tests/test_manual_all_enabled.py b/opentelemetry-configurator-gcp/tests/test_manual_all_enabled.py new file mode 100755 index 00000000..c7183da7 --- /dev/null +++ b/opentelemetry-configurator-gcp/tests/test_manual_all_enabled.py @@ -0,0 +1,22 @@ +#!./run_with_env.sh +import unittest + +import sys +sys.path.append('../src') + +from opentelemetry.configurator.gcp import OpenTelemetryGcpConfigurator + +class ManualTestWithAllOptionsEnabled(unittest.TestCase): + + def test_does_not_crash(self): + configurator = OpenTelemetryGcpConfigurator( + metrics_exporter_enabled=True, + logs_exporter_enabled=True, + traces_exporter_enabled=True, + resource_detector_enabled=True, + ) + configurator.configure() + + +if __name__ == '__main__': + unittest.main() diff --git a/opentelemetry-configurator-gcp/tests/test_manual_logging_only.py b/opentelemetry-configurator-gcp/tests/test_manual_logging_only.py new file mode 100755 index 00000000..d382947e --- /dev/null +++ b/opentelemetry-configurator-gcp/tests/test_manual_logging_only.py @@ -0,0 +1,21 @@ +#!./run_with_env.sh +import unittest + +import sys +sys.path.append('../src') + +from opentelemetry.configurator.gcp import OpenTelemetryGcpConfigurator + +class ManualTestLoggingOnly(unittest.TestCase): + + def test_does_not_crash(self): + configurator = OpenTelemetryGcpConfigurator( + metrics_exporter_enabled=False, + logs_exporter_enabled=True, + traces_exporter_enabled=False, + ) + configurator.configure() + + +if __name__ == '__main__': + unittest.main() diff --git a/opentelemetry-configurator-gcp/tests/test_manual_metrics_only.py b/opentelemetry-configurator-gcp/tests/test_manual_metrics_only.py new file mode 100755 index 00000000..e5814164 --- /dev/null +++ b/opentelemetry-configurator-gcp/tests/test_manual_metrics_only.py @@ -0,0 +1,21 @@ +#!./run_with_env.sh +import unittest + +import sys +sys.path.append('../src') + +from opentelemetry.configurator.gcp import OpenTelemetryGcpConfigurator + +class ManualTestMetricsOnly(unittest.TestCase): + + def test_does_not_crash(self): + configurator = OpenTelemetryGcpConfigurator( + metrics_exporter_enabled=True, + logs_exporter_enabled=False, + traces_exporter_enabled=False, + ) + configurator.configure() + + +if __name__ == '__main__': + unittest.main() diff --git a/opentelemetry-configurator-gcp/tests/test_manual_resource_off.py b/opentelemetry-configurator-gcp/tests/test_manual_resource_off.py new file mode 100755 index 00000000..ff140ac9 --- /dev/null +++ b/opentelemetry-configurator-gcp/tests/test_manual_resource_off.py @@ -0,0 +1,19 @@ +#!./run_with_env.sh +import unittest + +import sys +sys.path.append('../src') + +from opentelemetry.configurator.gcp import OpenTelemetryGcpConfigurator + +class ManualTestResourceOff(unittest.TestCase): + + def test_does_not_crash(self): + configurator = OpenTelemetryGcpConfigurator( + resource_detector_enabled=False, + ) + configurator.configure() + + +if __name__ == '__main__': + unittest.main() diff --git a/opentelemetry-configurator-gcp/tests/test_manual_resource_on.py b/opentelemetry-configurator-gcp/tests/test_manual_resource_on.py new file mode 100755 index 00000000..a4901c54 --- /dev/null +++ b/opentelemetry-configurator-gcp/tests/test_manual_resource_on.py @@ -0,0 +1,19 @@ +#!./run_with_env.sh +import unittest + +import sys +sys.path.append('../src') + +from opentelemetry.configurator.gcp import OpenTelemetryGcpConfigurator + +class ManualTestResourceOff(unittest.TestCase): + + def test_does_not_crash(self): + configurator = OpenTelemetryGcpConfigurator( + resource_detector_enabled=True, + ) + configurator.configure() + + +if __name__ == '__main__': + unittest.main() diff --git a/opentelemetry-configurator-gcp/tests/test_manual_tracing_only.py b/opentelemetry-configurator-gcp/tests/test_manual_tracing_only.py new file mode 100755 index 00000000..8c35e082 --- /dev/null +++ b/opentelemetry-configurator-gcp/tests/test_manual_tracing_only.py @@ -0,0 +1,21 @@ +#!./run_with_env.sh +import unittest + +import sys +sys.path.append('../src') + +from opentelemetry.configurator.gcp import OpenTelemetryGcpConfigurator + +class ManualTestTracingOnly(unittest.TestCase): + + def test_does_not_crash(self): + configurator = OpenTelemetryGcpConfigurator( + metrics_exporter_enabled=False, + logs_exporter_enabled=False, + traces_exporter_enabled=True, + ) + configurator.configure() + + +if __name__ == '__main__': + unittest.main() diff --git a/opentelemetry-configurator-gcp/tools/build.sh b/opentelemetry-configurator-gcp/tools/build.sh index 9e0a55ad..3ee3a15c 100755 --- a/opentelemetry-configurator-gcp/tools/build.sh +++ b/opentelemetry-configurator-gcp/tools/build.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -o pipefail + SCRIPT_DIR=$(cd $(dirname ${BASH_SOURCE:-0}); pwd) PROJECT_DIR=$(readlink -f "${SCRIPT_DIR}/..") BUILD_DIR="${PROJECT_DIR}/.build" @@ -7,17 +9,17 @@ BUILD_ENV="${BUILD_DIR}/.venv" function main() { if [ ! -d "${BUILD_ENV}" ] ; then - mkdir -p "${BUILD_ENV}" + mkdir -p "${BUILD_ENV}" || exit 1 fi if [ ! -d "${BUILD_ENV}/bin" ] ; then - python3 -m venv "${BUILD_ENV}" + python3 -m venv "${BUILD_ENV}" || exit 1 fi - source "${BUILD_ENV}/bin/activate" - pip install build + source "${BUILD_ENV}/bin/activate" || exit 1 + pip install build || exit 1 - cd "${PROJECT_DIR}" - python3 -m build + cd "${PROJECT_DIR}" || exit 1 + python3 -m build || exit $? } main diff --git a/opentelemetry-configurator-gcp/tools/test.sh b/opentelemetry-configurator-gcp/tools/test.sh index e69de29b..70474d2b 100755 --- a/opentelemetry-configurator-gcp/tools/test.sh +++ b/opentelemetry-configurator-gcp/tools/test.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -o pipefail + +SCRIPT_DIR=$(cd $(dirname ${BASH_SOURCE:-0}); pwd) +PROJECT_DIR=$(readlink -f "${SCRIPT_DIR}/..") +TESTS_DIR="${PROJECT_DIR}/tests" +TEST_ENV="${PROJECT_DIR}/.test/.venv" + +function main() { + if [ ! -d "${TEST_ENV}" ] ; then + mkdir -p "${TEST_ENV}" || exit 1 + fi + if [ ! -d "${TEST_ENV}/bin" ] ; then + python3 -m venv "${TEST_ENV}" || exit 1 + fi + + source "${TEST_ENV}/bin/activate" || exit 1 + pip install pytest || exit $? + pip install -r "${TESTS_DIR}/requirements.txt" || exit $? + + cd "${PROJECT_DIR}" || exit 1 + export PYTHONPATH="${PYTHONPATH}:${PROJECT_DIR}/src" + pytest "${TESTS_DIR}" -o log_cli_level=debug $@ || exit $? +} + +main From ab475508d77ac6092203dd59d2dc7569d8164ffc Mon Sep 17 00:00:00 2001 From: Michael Aaron Safyan Date: Fri, 14 Feb 2025 14:59:07 -0500 Subject: [PATCH 11/13] Fill out the README.rst file. --- opentelemetry-configurator-gcp/README.rst | 57 +++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/opentelemetry-configurator-gcp/README.rst b/opentelemetry-configurator-gcp/README.rst index 25fb96e6..70f3591f 100644 --- a/opentelemetry-configurator-gcp/README.rst +++ b/opentelemetry-configurator-gcp/README.rst @@ -1,3 +1,60 @@ OpenTelemetry Google Cloud Configurator ======================================== +Purpose +------- +Simplifies configuring the Open Telemetry library to write to Google Cloud Observability backends. + + +Usage +----- + +There are two ways that this can be used: + + 1. With automatic instrumentation. + 2. With manual instrumentation. + + +Automatic Instrumentation +^^^^^^^^^^^^^^^^^^^^^^^^^ + +To use this component with automatic instrumentation, simply invoke +``opentelemetry-instrument`` with the argument ``--configurator=gcp``. + +:: + + opentelemetry-instrument \ + --configurator=gcp \ + python \ + the/path/to/your/code.py + +See `Python zero-code instrumentation for Python `_ + + +Manual Instrumentation +^^^^^^^^^^^^^^^^^^^^^^ + +You can also call the configurator code manually. + +:: + + from opentelemetry.configurator.gcp import OpenTelemetryGcpConfigurator + + OpenTelemetryGcpConfigurator().configure() + + +Installation +------------ + +You can use a standard Python package management tool like ``pip`` or ``uv`` to install. + +The PyPi package is ``opentelemetry-configurator-gcp``. + +For the automatic instrumentation, you must additionally install ``opentelemetry-distro``. + + +References +---------- + +* `Python zero-code instrumentation for Python `_ +* `Google Cloud Observability `_ From a9e3cb5751a67f08b31651add3b3f993d209b66a Mon Sep 17 00:00:00 2001 From: Michael Aaron Safyan Date: Fri, 14 Feb 2025 15:04:52 -0500 Subject: [PATCH 12/13] Added the ability to lint the code. --- opentelemetry-configurator-gcp/.gitignore | 5 ++++ opentelemetry-configurator-gcp/tools/lint.sh | 27 ++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 opentelemetry-configurator-gcp/.gitignore diff --git a/opentelemetry-configurator-gcp/.gitignore b/opentelemetry-configurator-gcp/.gitignore new file mode 100644 index 00000000..89ea5907 --- /dev/null +++ b/opentelemetry-configurator-gcp/.gitignore @@ -0,0 +1,5 @@ +.build +build +dist +.venv + diff --git a/opentelemetry-configurator-gcp/tools/lint.sh b/opentelemetry-configurator-gcp/tools/lint.sh index e69de29b..94a5e175 100755 --- a/opentelemetry-configurator-gcp/tools/lint.sh +++ b/opentelemetry-configurator-gcp/tools/lint.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -o pipefail + +SCRIPT_DIR=$(cd $(dirname ${BASH_SOURCE:-0}); pwd) +PROJECT_DIR=$(readlink -f "${SCRIPT_DIR}/..") +BUILD_DIR="${PROJECT_DIR}/.build" +LINT_ENV="${BUILD_DIR}/.lint-venv" +LINT_REQUIREMENTS="${PROJECT_DIR}/tests/requirements.txt" + +function main() { + if [ ! -d "${LINT_ENV}" ] ; then + mkdir -p "${LINT_ENV}" || exit 1 + fi + if [ ! -d "${LINT_ENV}/bin" ] ; then + python3 -m venv "${LINT_ENV}" || exit 1 + fi + + source "${LINT_ENV}/bin/activate" || exit 1 + pip install -r ${LINT_REQUIREMENTS} || exit 1 + pip install pylint || exit 1 + + cd "${PROJECT_DIR}" || exit 1 + pylint src +} + +main From 68539d5e69b81caf1807be5eb848eb32571dd97b Mon Sep 17 00:00:00 2001 From: Michael Aaron Safyan Date: Fri, 14 Feb 2025 15:54:15 -0500 Subject: [PATCH 13/13] Address lint issues. --- .../configurator/gcp/__init__.py | 28 +++++++++ .../configurator/gcp/configurator.py | 56 +++++++++++++++++- .../opentelemetry/configurator/gcp/flags.py | 34 ++++++++++- .../configurator/gcp/gcloud_env.py | 57 ++++++++++++++++++- .../opentelemetry/configurator/gcp/logs.py | 31 +++++----- .../opentelemetry/configurator/gcp/metrics.py | 10 ++++ .../configurator/gcp/resource.py | 30 +++++++--- .../opentelemetry/configurator/gcp/traces.py | 17 +++++- .../opentelemetry/configurator/gcp/version.py | 2 + 9 files changed, 228 insertions(+), 37 deletions(-) diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py index 561c7fb6..cef899ec 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py @@ -1,3 +1,31 @@ +"""Open Telemetry configurator for Google Cloud + +This package provides the 'OpenTelemetryGcpConfigurator' which simplifies configuration of the +Open Telemetry library to route logs, traces, and metrics to Google Cloud Observability products +such as Cloud Logging, Cloud Trace, and Cloud Monitoring. + +The OpenTelemetryGcpConfigurator can be invoked directly as in: + + from opentelemetry.configurator.gcp import OpenTelemetryGcpConfigurator + + OpenTelemetryGcpConfigurator().configure() + +It can also be invoked automatically from the "opentelemetry-instrument" command, +which is part of the Open Telemetry zero-code instrumentation for Python. To +invoke it automatically, simply supply "--configurator=gcp" as a commandline +flag to the "opentelemetry-instrument" command. As an example: + + opentelemetry-instrument \ + --configurator=gcp \ + python \ + the/path/to/your/script.py + +This automatic wiring is implemented using the registration mechanism in "pyproject.toml"; +in particular, the "[project.entry-points.opentelemetry_configurator]" entry in that file +makes this component known to the auto-instrumentation system. And it being a class +that defines a "configure(self, **kwargs)" method makes it compatible with that API. +.""" + from .configurator import OpenTelemetryGcpConfigurator from .version import __version__ diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py index ed644103..d1c3f61e 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py @@ -1,3 +1,4 @@ +"""Defines the 'OpenTelemetryGcpConfigurator' class for simplifying Open Telemetry setup for GCP.""" from typing import Optional, Callable from .flags import ( @@ -18,7 +19,8 @@ def _bool_with_flag_default(value: Optional[bool], flag_lookup: Callable[None, b return flag_lookup() -class OpenTelemetryGcpConfigurator: +class OpenTelemetryGcpConfigurator: # pylint: disable=too-few-public-methods + """A class that can be used as a configurator in Open Telemetry zero-code instrumentation.""" def __init__( self, @@ -26,6 +28,43 @@ def __init__( logs_exporter_enabled:Optional[bool]=None, traces_exporter_enabled:Optional[bool]=None, resource_detector_enabled:Optional[bool]=None): + """Initialize the configurator with optional parameters to direct the behavior. + + No arguments are supplied when invoked from the zero-configuration system. + + Args: + metrics_exporter_enabled: whether to enable metrics export. If unset, + falls back to an environment variable, allowing this argument to + be supplied even in zero-configuration scenarios. If that, too, + is unset, then the metrics export will be enabled. + + logs_exporter_enabled: whether to enable logs export. If unset, + falls back to an environment variable, allowing this argument to + be supplied even in zero-configuration scenarios. If that, too, + is unset, then the logs export will be enabled. + + traces_exporter_enabled: whether to enable trace export. If unset, + falls back to an environment variable, allowing this argument to + be supplied even in zero-configuration scenarios. If that, too, + is unset, then the trace export will be enabled. + + resource_detector_enabled: whether to enable the GCP resource + detector (which is useful only when the code is running in + a GCP environment). If unset, falls back to an environment variable, + allowing this argument to be supplied even in zero-configuration + scenarios. If that, too, is unset, then the code will attempt + to determine if the code is likely deployed in GCP or not + based on the environment to enable the detector or not. + + Environment Variables: + + The following environment variables affect the defaults: + + - OTEL_GCP_METRICS_EXPORTER_ENABLED + - OTEL_GCP_LOGS_EXPORTER_ENABLED + - OTEL_GCP_TRACES_EXPORTER_ENABLED + - OTEL_GCP_RESOURCE_DETECTOR_ENABLED + """ self._metrics_exporter_enabled = _bool_with_flag_default( metrics_exporter_enabled, is_metrics_exporter_enabled) self._logs_exporter_enabled = _bool_with_flag_default( @@ -35,7 +74,19 @@ def __init__( self._resource_detector_enabled = _bool_with_flag_default( resource_detector_enabled, is_resource_detector_enabled) - def configure(self, **kwargs): + def configure(self, **unused_kwargs): + """Configure the Open Telemetry library to talk to GCP backends. + + This function configures the Open Telemetry library to talk to GCP + backends, subject to the class initialization parameters. + + Although this class does not inherit any explicit interface, this + function should be treated like an inherited method in that its + signature is dictated by the Open Telemetry auto-instrumentation. + + Uses **unused_kwargs to allow future iterations of the Open Telemetry + library to introduce additional keyword arguments without breaking. + """ resource = get_resource(include_gcp_detector=self._resource_detector_enabled) if self._metrics_exporter_enabled: configure_metrics_exporter(resource=resource) @@ -43,4 +94,3 @@ def configure(self, **kwargs): configure_logs_exporter(resource=resource) if self._traces_exporter_enabled: configure_traces_exporter(resource=resource) - diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py index c0c0727f..f9a8e76d 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py @@ -1,9 +1,25 @@ +"""Provides functions for querying the configuration of this library. + +Other modules in this package use the 'flags' library to obtain default +behaviors where the behavior has not been explicitly specified. +""" +from typing import Optional + import os from . import gcloud_env -def _str_to_optional_bool(s): +def _str_to_optional_bool(s: str) -> Optional[bool]: + """Converts a string to an optional boolean. + + Args: + s: the string to convert to a boolean + + Returns: + A boolean if the value is clearly false or clearly true. + None if the string does not match a known true/false pattern. + """ lower_s = s.lower() if lower_s in ['1', 'true', 't', 'y', 'yes', 'on']: return True @@ -12,7 +28,17 @@ def _str_to_optional_bool(s): return None -def _get_bool_flag_from_env(env_var_name, default_value=None): +def _get_bool_flag_from_env(env_var_name: str, default_value:Optional[bool]=None) -> Optional[bool]: + """Retrieves a boolean value from an environment variable. + + Args: + env_var_name: The name of the environment variable to retrieve. + default_value: The value to return if unset or has a non-bool value. + + Returns: + The boolean value of the environment variable if set and valid. + Otherwise, falls back to the supplied default value. + """ s = os.getenv(env_var_name) if s is None: return default_value @@ -23,6 +49,7 @@ def _get_bool_flag_from_env(env_var_name, default_value=None): def is_metrics_exporter_enabled(): + """Returns whether to enable metrics exporting by default.""" return _get_bool_flag_from_env( 'OTEL_GCP_METRICS_EXPORTER_ENABLED', default_value=True @@ -30,6 +57,7 @@ def is_metrics_exporter_enabled(): def is_logs_exporter_enabled(): + """Returns whether to enable logs exporting by default.""" return _get_bool_flag_from_env( 'OTEL_GCP_LOGS_EXPORTER_ENABLED', default_value=True @@ -37,6 +65,7 @@ def is_logs_exporter_enabled(): def is_traces_exporter_enabled(): + """Returns whether to enable trace exporting by default.""" return _get_bool_flag_from_env( 'OTEL_GCP_TRACES_EXPORTER_ENABLED', default_value=True @@ -44,6 +73,7 @@ def is_traces_exporter_enabled(): def is_resource_detector_enabled(): + """Returns whether to enable the GCP resource detector by default.""" result = _get_bool_flag_from_env( 'OTEL_GCP_RESOURCE_DETECTOR_ENABLED') if result is not None: diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/gcloud_env.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/gcloud_env.py index 723d9251..8258b487 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/gcloud_env.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/gcloud_env.py @@ -1,23 +1,50 @@ +"""Provides 'is_running_on_gcp' to determine whether to enable the GCP resource detector.""" import os import os.path import socket def _can_resolve_metadata_server(): + """Returns whether the GCP metadata server address can be resolved. + + On GCP, there is a special 'metadata.google.internal' DNS name that is + used to supply metadata about the environment. Although it is possible + to edit "/etc/hosts" to introduce a similarly-named service outside + of GCP, the existence of this name is a strong hint of running in GCP. + """ try: socket.getaddrinfo('metadata.google.internal', 80) return True - except: + except OSError: return False def _is_likely_gae(): + """Returns whether env vars indicate a GAE environment. + + The Google App Engine documentation calls out several of these + environment variables as being automatically setup by the runtime. + + Although it is possible to set these manually outside of GAE, the + presence of these in conjunction with the presence of the metadata + server provides a strong hint of running within GAE. + """ return (('GAE_APPLICATION' in os.environ) and ('GAE_DEPLOYMENT_ID' in os.environ) and ('GAE_SERVICE' in os.environ)) def _is_likely_cloud_run(): + """Returns whether env vars indicate a Cloud Run environment. + + The Cloud Run documentation calls out several of these + environment variables as being automatically setup by the runtime. + + Some of these may aslo be present when running K-Native in Kubernetes; + however, the presence of these environment variables in conjunction + with the presence of the metadata server provide a strong hint + that the code is running inside of Cloud Run. + """ return ( ('K_SERVICE' in os.environ) and ('K_REVISION' in os.environ) and @@ -25,14 +52,39 @@ def _is_likely_cloud_run(): def _is_likely_gce(): + """Returns whether there is evidence of running in GCE. + + The given pre-supplied paths are called out in GCE documentation + and are not likely to exist in other environments. In conjunction + with the existing of the metadata server, the checks here provide + supportive evidence of running within a GCE environment. + """ return os.path.exists('/run/google-mds-mtls') def _is_likely_gke(): - return ('KUBERNETES_SERVICE_HOST' in os.environ) + """Returns whether there is evidence of runing in GKE. + + Although also applicable to Kubernetes outside of GCP, + the evidence of Kubernetes in conjunction with the presence + of the GCP metadata server strongly hints at GKE. + """ + return 'KUBERNETES_SERVICE_HOST' in os.environ def is_running_on_gcp(): + """Returns whether the code is probably running on GCP. + + This is not intended to be 100% bullet proof nor + comprehensive of the entire GCP ecosystem; rather, it + is intended to be "good enough" to determine whether to + pay the additional costs of GCP resource detection. + + That is, it should ideally be light-weight (if it's too + expensive, you might as well always do GCP resource + detection), and it should ideally cover the subset + of GCP environments for which resource detction exists. + """ return ( _can_resolve_metadata_server() and ( @@ -42,4 +94,3 @@ def is_running_on_gcp(): _is_likely_gae() ) ) - diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py index a5479c61..7a2b662d 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py @@ -1,4 +1,4 @@ -import sys +"""Provides a mechanism to configure the Logs Exporter for GCP.""" import os import os.path import logging @@ -19,21 +19,6 @@ } -def _get_entrypoint_script_name(): - main_script_path = sys.argv[0] - if not main_script_path: - main_script_path = sys.executable - simple_script_name = os.path.basename(main_script_path).rstrip('.py') - return simple_script_name - - -def _get_log_name(): - log_name = os.getenv('OTEL_GCP_LOG_NAME') - if log_name: - return log_name - return _get_entrypoint_script_name() - - def _get_log_level(): level = os.getenv(otel_env_vars.OTEL_LOG_LEVEL) if level is None: @@ -45,9 +30,19 @@ def _get_log_level(): def configure_logs_exporter(resource=None): + """Configures the Cloud Logging exporter. + + Args: + resource: the resource to include in the emitted logs + + Effects: + - Invokes the 'set_logger_provider' function with an + exporter that will cause OTel logs to get routed to GCP. + - Modifies the built-in 'logging' component in Python to + route built-in Python logs to Open Telemetry. + """ provider = LoggerProvider(resource=resource) - provider.add_log_record_processor(BatchLogRecordProcessor( - CloudLoggingExporter(default_log_name=_get_log_name()))) + provider.add_log_record_processor(BatchLogRecordProcessor(CloudLoggingExporter())) set_logger_provider(provider) handler = LoggingHandler(level=_get_log_level(), logger_provider=provider) logging.getLogger().addHandler(handler) diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py index b249b5f2..2d55c085 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py @@ -1,3 +1,4 @@ +"""Provides a mechanism to configure the Metrics Exporter for GCP.""" from opentelemetry import metrics as otel_metrics from opentelemetry.sdk import metrics as otel_metrics_sdk from opentelemetry.sdk.metrics import export as otel_metrics_sdk_export @@ -5,6 +6,15 @@ def configure_metrics_exporter(resource=None): + """Configures the Open Telemetry metrics library to write to Cloud Monitoring. + + Args: + resource: The resource to use when writing metrics. + + Effects: + Calls 'set_meter_provider' with a MeterProvider that will cause + Open Telemetry metrics to get routed to Cloud Monitoring. + """ provider = otel_metrics_sdk.MeterProvider( metric_readers=[ otel_metrics_sdk_export.PeriodicExportingMetricReader( diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py index a9790294..9702483d 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py @@ -1,13 +1,27 @@ +"""Provides a mechanism to configure the Resource Detector for GCP.""" from opentelemetry.sdk import resources as otel_resources_sdk from opentelemetry.resourcedetector import gcp_resource_detector def get_resource(include_gcp_detector=False): - detectors = [ - otel_resources_sdk.OTELResourceDetector(), - otel_resources_sdk.ProcessResourceDetector(), - otel_resources_sdk.OsResourceDetector(), - ] - if include_gcp_detector: - detectors.append(gcp_resource_detector.GoogleCloudResourceDetector()) - return otel_resources_sdk.get_aggregated_resources(detectors) + """Calculate the resource to use in Open Telemetry signals. + + Args: + include_gcp_detector: Whether to merge in information about + the GCP environment in which the code is running. + + Effects: + Gathers information about the current environment to produce + a resource that summarizes the running environment. + + Returns: + A resource that summarizes the environment. + """ + detectors = [ + otel_resources_sdk.OTELResourceDetector(), + otel_resources_sdk.ProcessResourceDetector(), + otel_resources_sdk.OsResourceDetector(), + ] + if include_gcp_detector: + detectors.append(gcp_resource_detector.GoogleCloudResourceDetector()) + return otel_resources_sdk.get_aggregated_resources(detectors) diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py index fad08bdc..6237f621 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py @@ -1,3 +1,4 @@ +"""Provides a mechanism to configure the Traces Exporter for GCP.""" from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter @@ -5,6 +6,16 @@ def configure_traces_exporter(resource=None): - provider = TracerProvider(resource=resource) - provider.add_span_processor(BatchSpanProcessor(CloudTraceSpanExporter())) - trace.set_tracer_provider(provider) + """Configures the Open Telemetry tracing libraries to write to Cloud Trace. + + Args: + - resource: The resource to include when writing trace data. + + Effects: + + Calls the 'set_tracer_provider' operation with a TracerProvider that + will cause traces to be written to the Cloud Trace backend. + """ + provider = TracerProvider(resource=resource) + provider.add_span_processor(BatchSpanProcessor(CloudTraceSpanExporter())) + trace.set_tracer_provider(provider) diff --git a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/version.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/version.py index 15addcbc..273965fb 100644 --- a/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/version.py +++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/version.py @@ -1 +1,3 @@ +"""Defines the version of this library.""" + __version__ = "0.0.1.dev0"