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/Makefile b/opentelemetry-configurator-gcp/Makefile
new file mode 100644
index 00000000..748801b1
--- /dev/null
+++ 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/README.rst b/opentelemetry-configurator-gcp/README.rst
new file mode 100644
index 00000000..70f3591f
--- /dev/null
+++ b/opentelemetry-configurator-gcp/README.rst
@@ -0,0 +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 `_
diff --git a/opentelemetry-configurator-gcp/pyproject.toml b/opentelemetry-configurator-gcp/pyproject.toml
new file mode 100644
index 00000000..c75249fe
--- /dev/null
+++ b/opentelemetry-configurator-gcp/pyproject.toml
@@ -0,0 +1,27 @@
+[build-system]
+requires = ["hatchling"]
+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:OpenTelemetryGcpConfigurator"
+
+[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/__init__.py b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py
new file mode 100644
index 00000000..cef899ec
--- /dev/null
+++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/__init__.py
@@ -0,0 +1,35 @@
+"""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__
+
+__all__ = [
+ "OpenTelemetryGcpConfigurator",
+ "__version__",
+]
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..d1c3f61e
--- /dev/null
+++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/configurator.py
@@ -0,0 +1,96 @@
+"""Defines the 'OpenTelemetryGcpConfigurator' class for simplifying Open Telemetry setup for GCP."""
+from typing import Optional, Callable
+
+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: # pylint: disable=too-few-public-methods
+ """A class that can be used as a configurator in Open Telemetry zero-code instrumentation."""
+
+ 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):
+ """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(
+ 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, **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)
+ 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..f9a8e76d
--- /dev/null
+++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/flags.py
@@ -0,0 +1,81 @@
+"""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: 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
+ if lower_s in ['0', 'false', 'f', 'n', 'no', 'off']:
+ return False
+ return 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
+ result = _str_to_optional_bool(s)
+ if result is not None:
+ return result
+ return default_value
+
+
+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
+ )
+
+
+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
+ )
+
+
+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
+ )
+
+
+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:
+ 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..8258b487
--- /dev/null
+++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/gcloud_env.py
@@ -0,0 +1,96 @@
+"""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 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
+ ('K_CONFIGURATION' in os.environ))
+
+
+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():
+ """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
+ (
+ _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
new file mode 100644
index 00000000..7a2b662d
--- /dev/null
+++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/logs.py
@@ -0,0 +1,48 @@
+"""Provides a mechanism to configure the Logs Exporter for GCP."""
+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_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):
+ """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()))
+ 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
new file mode 100644
index 00000000..2d55c085
--- /dev/null
+++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/metrics.py
@@ -0,0 +1,25 @@
+"""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
+from opentelemetry.exporter import cloud_monitoring as otel_cloud_monitoring
+
+
+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(
+ otel_cloud_monitoring.CloudMonitoringMetricsExporter()
+ ),
+ ],
+ resource=resource)
+ otel_metrics.set_meter_provider(provider)
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..9702483d
--- /dev/null
+++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/resource.py
@@ -0,0 +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):
+ """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
new file mode 100644
index 00000000..6237f621
--- /dev/null
+++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/traces.py
@@ -0,0 +1,21 @@
+"""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
+from opentelemetry.sdk.trace.export import BatchSpanProcessor
+
+
+def configure_traces_exporter(resource=None):
+ """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
new file mode 100644
index 00000000..273965fb
--- /dev/null
+++ b/opentelemetry-configurator-gcp/src/opentelemetry/configurator/gcp/version.py
@@ -0,0 +1,3 @@
+"""Defines the version of this library."""
+
+__version__ = "0.0.1.dev0"
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_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/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_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_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/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
new file mode 100755
index 00000000..3ee3a15c
--- /dev/null
+++ b/opentelemetry-configurator-gcp/tools/build.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+set -o pipefail
+
+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}" || exit 1
+ fi
+ if [ ! -d "${BUILD_ENV}/bin" ] ; then
+ python3 -m venv "${BUILD_ENV}" || exit 1
+ fi
+
+ source "${BUILD_ENV}/bin/activate" || exit 1
+ pip install build || exit 1
+
+ cd "${PROJECT_DIR}" || exit 1
+ python3 -m build || exit $?
+}
+
+main
diff --git a/opentelemetry-configurator-gcp/tools/lint.sh b/opentelemetry-configurator-gcp/tools/lint.sh
new file mode 100755
index 00000000..94a5e175
--- /dev/null
+++ 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
diff --git a/opentelemetry-configurator-gcp/tools/test.sh b/opentelemetry-configurator-gcp/tools/test.sh
new file mode 100755
index 00000000..70474d2b
--- /dev/null
+++ 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