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