From d02093753d1322f81c6cb1cbcb15806e25b92dea Mon Sep 17 00:00:00 2001 From: John Bley Date: Fri, 7 Jun 2024 10:58:27 -0400 Subject: [PATCH 01/30] Partial work towards using upstream configurator and removing our custom initialization code --- pyproject.toml | 3 + splunk_otel/defaults.py | 1 + splunk_otel/distro.py | 30 ++------ splunk_otel/environment_variables.py | 2 - splunk_otel/metrics.py | 49 +------------ splunk_otel/options.py | 102 ++------------------------- splunk_otel/profiling/__init__.py | 4 +- splunk_otel/tracing.py | 50 ++----------- 8 files changed, 25 insertions(+), 216 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 280f236d..c0dfed2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,9 @@ splk-py-trace-bootstrap = 'splunk_otel.cmd.bootstrap:run_deprecated' [tool.poetry.plugins."opentelemetry_distro"] splunk_distro = "splunk_otel.distro:_SplunkDistro" +[tool.poetry.plugins."opentelemetry_configurator"] +configurator = "splunk_otel.distro:SplunkConfigurator" + [tool.poetry.dependencies] cryptography=">=2.0,<=42.0.8" python = ">=3.8" diff --git a/splunk_otel/defaults.py b/splunk_otel/defaults.py index e07eb246..e99891ca 100644 --- a/splunk_otel/defaults.py +++ b/splunk_otel/defaults.py @@ -14,6 +14,7 @@ from os import environ +# FIXME remove this _OTEL_PYTHON_LOG_CORRELATION = "OTEL_PYTHON_LOG_CORRELATION" diff --git a/splunk_otel/distro.py b/splunk_otel/distro.py index bb4444e0..27441f86 100644 --- a/splunk_otel/distro.py +++ b/splunk_otel/distro.py @@ -19,46 +19,26 @@ from opentelemetry.instrumentation.distro import BaseDistro # type: ignore from pkg_resources import EntryPoint -from splunk_otel.metrics import _configure_metrics from splunk_otel.options import _Options from splunk_otel.profiling import _start_profiling from splunk_otel.profiling.options import _Options as ProfilingOptions -from splunk_otel.tracing import _configure_tracing from splunk_otel.util import _is_truthy +from opentelemetry.sdk._configuration import _OTelSDKConfigurator logger = logging.getLogger(__name__) +class SplunkConfigurator(_OTelSDKConfigurator): + pass + class _SplunkDistro(BaseDistro): def __init__(self): - tracing_enabled = os.environ.get("OTEL_TRACE_ENABLED", True) profiling_enabled = os.environ.get("SPLUNK_PROFILER_ENABLED", False) - self._tracing_enabled = _is_truthy(tracing_enabled) self._profiling_enabled = _is_truthy(profiling_enabled) - if not self._tracing_enabled: - logger.info( - "tracing has been disabled with OTEL_TRACE_ENABLED=%s", tracing_enabled - ) - - metrics_enabled = os.environ.get("OTEL_METRICS_ENABLED", True) - self._metrics_enabled = _is_truthy(metrics_enabled) - if not self._metrics_enabled: - logger.info( - "metering has been disabled with OTEL_METRICS_ENABLED=%s", metrics_enabled - ) def _configure(self, **kwargs: Dict[str, Any]) -> None: options = _Options() - - if self._tracing_enabled: - _configure_tracing(options) + # FIXME _configure_Tracing and _metrics might have some unique stuff to copy thrugh to here if self._profiling_enabled: _start_profiling(ProfilingOptions(options.resource)) - - if self._metrics_enabled: - _configure_metrics() - - def load_instrumentor(self, entry_point: EntryPoint, **kwargs): - if self._tracing_enabled: - super().load_instrumentor(entry_point, **kwargs) diff --git a/splunk_otel/environment_variables.py b/splunk_otel/environment_variables.py index ea2d1fb2..7ee6eae0 100644 --- a/splunk_otel/environment_variables.py +++ b/splunk_otel/environment_variables.py @@ -14,5 +14,3 @@ _SPLUNK_ACCESS_TOKEN = "SPLUNK_ACCESS_TOKEN" _SPLUNK_TRACE_RESPONSE_HEADER_ENABLED = "SPLUNK_TRACE_RESPONSE_HEADER_ENABLED" -_OTEL_METRICS_ENABLED = "OTEL_METRICS_ENABLED" -_OTEL_LOG_LEVEL = "OTEL_LOG_LEVEL" diff --git a/splunk_otel/metrics.py b/splunk_otel/metrics.py index d0d9b979..5413cb57 100644 --- a/splunk_otel/metrics.py +++ b/splunk_otel/metrics.py @@ -20,52 +20,9 @@ from splunk_otel.util import _is_truthy logger = logging.getLogger(__name__) +from opentelemetry.metrics import get_meter_provider def start_metrics() -> MeterProvider: - # pylint: disable=import-outside-toplevel - from opentelemetry.instrumentation.system_metrics import SystemMetricsInstrumentor - - enabled = os.environ.get("OTEL_METRICS_ENABLED", True) - if not _is_truthy(enabled): - logger.info("metering has been disabled with OTEL_METRICS_ENABLED=%s", enabled) - return None - - try: - meter_provider = _configure_metrics() - system_metrics = SystemMetricsInstrumentor() - system_metrics.instrument(meter_provider=meter_provider) - logger.debug("Instrumented runtime metrics") - return meter_provider - except Exception as exc: # pylint:disable=broad-except - logger.exception("Instrumenting of runtime metrics failed") - raise exc - - -def _configure_metrics() -> MeterProvider: - # pylint: disable=import-outside-toplevel - # FIXME yes, this doesn't match up to the spec in terms of looking for - # relevant environment variables, etc., but this is a stopgap because a lot - # of this initialization sequence needs to be burned down and replaced with the - # upstream SDK's Configurator. - metrics_exporter = None - try: - from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( - OTLPMetricExporter, - ) - - metrics_exporter = OTLPMetricExporter() - except Exception: # pylint:disable=broad-except - logger.info("Cannot load grpc metrics exporter, trying http/protobuf") - from opentelemetry.exporter.otlp.proto.http.metric_exporter import ( - OTLPMetricExporter, - ) - - metrics_exporter = OTLPMetricExporter() - - from opentelemetry.metrics import set_meter_provider - from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader - - meter_provider = MeterProvider([PeriodicExportingMetricReader(metrics_exporter)]) - set_meter_provider(meter_provider) - return meter_provider + # FIXME mark deprecated and/or log warning + return get_meter_provider() diff --git a/splunk_otel/options.py b/splunk_otel/options.py index 3ff495ea..a3d33955 100644 --- a/splunk_otel/options.py +++ b/splunk_otel/options.py @@ -64,19 +64,16 @@ logger = logging.getLogger("options") +# FIXME possibly deal with one-off customer issues with documenting how to customize +# span exporters with stock otel apis class _Options: - span_exporter_factories: Collection[_SpanExporterFactory] access_token: Optional[str] - resource: Resource response_propagation: bool response_propagator: Optional[ResponsePropagator] def __init__( self, - service_name: Optional[str] = None, - span_exporter_factories: Optional[Collection[_SpanExporterFactory]] = None, access_token: Optional[str] = None, - resource_attributes: Optional[Dict[str, Union[str, bool, int, float]]] = None, trace_response_header_enabled: Optional[bool] = None, ): # todo: remove this side effect @@ -84,10 +81,6 @@ def __init__( self.access_token = _resolve_access_token(access_token) self.response_propagator = _get_response_propagator(trace_response_header_enabled) - self.resource = _create_resource(service_name, resource_attributes) - self.span_exporter_factories = _get_span_exporter_factories( - span_exporter_factories - ) def _resolve_access_token(access_token: Optional[str]) -> Optional[str]: @@ -108,40 +101,12 @@ def _get_response_propagator( return None -def _create_resource( - service_name: Optional[str], - attributes: Optional[Dict[str, Union[str, bool, int, float]]], -) -> Resource: - attributes = attributes or {} - if service_name: - attributes[_SERVICE_NAME_ATTR] = service_name - attributes.update( - { - _TELEMETRY_VERSION_ATTR: auto_instrumentation_version, - _SPLUNK_DISTRO_VERSION_ATTR: __version__, - } - ) - resource = Resource.create(attributes) - if ( - resource.attributes.get(_SERVICE_NAME_ATTR, _DEFAULT_OTEL_SERVICE_NAME) - == _DEFAULT_OTEL_SERVICE_NAME - ): - logger.warning(_NO_SERVICE_NAME_WARNING) - resource = resource.merge(Resource({_SERVICE_NAME_ATTR: _DEFAULT_SERVICE_NAME})) - return resource - - -def _get_span_exporter_factories( - factories: Optional[Collection[_SpanExporterFactory]], -) -> Collection[_SpanExporterFactory]: - if factories: - return factories - - exporter_names = _get_span_exporter_names_from_env() - return _import_span_exporter_factories(exporter_names) +# FIXME need our splunk.distro.version attribute added to the resource +# _SPLUNK_DISTRO_VERSION_ATTR: __version__, def _set_default_env() -> None: + # FIXME audit this for same-as-upstream or unique-to-us defaults = { OTEL_ATTRIBUTE_COUNT_LIMIT: _LIMIT_UNSET_VALUE, OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: _LIMIT_UNSET_VALUE, @@ -157,61 +122,6 @@ def _set_default_env() -> None: environ[key] = value -def _get_span_exporter_names_from_env() -> Collection[Tuple[str, str]]: - exporters_env = environ.get(OTEL_TRACES_EXPORTER, "").strip() or _DEFAULT_EXPORTERS - - exporters: List[Tuple[str, str]] = [] - if not _is_truthy_str(exporters_env): - return exporters - - # exporters are known by different names internally by Python Otel SDK. - # Here we create a mapping of user provided names to internal names so - # that we can provide helpful error messages later. - for name in exporters_env.split(","): - if name == _EXPORTER_OTLP: - exporters.append((_EXPORTER_OTLP, _EXPORTER_OTLP_GRPC)) - else: - exporters.append( - ( - name, - name, - ) - ) - return exporters - - -def _import_span_exporter_factories( - exporter_names: Collection[Tuple[str, str]], -) -> Collection[_SpanExporterFactory]: - factories = [] - entry_points = { - ep.name: ep for ep in iter_entry_points("opentelemetry_traces_exporter") - } - - for name, internal_name in exporter_names: - if internal_name not in entry_points: - package = _KNOWN_EXPORTER_PACKAGES.get(internal_name) - if package: - help_msg = f"please make sure {package} is installed" - else: - help_msg = "please make sure the relevant exporter package is installed." - raise ValueError(f'exporter "{name} ({internal_name})" not found. {help_msg}') - - exporter_class: _SpanExporterClass = entry_points[internal_name].load() - if internal_name == _EXPORTER_OTLP_GRPC: - factory = _otlp_factory - else: - factory = _generic_exporter - factories.append(partial(factory, exporter_class)) - return factories - - -def _generic_exporter( - exporter: _SpanExporterClass, - options: _Options, # pylint: disable=unused-argument -) -> SpanExporter: - return exporter() - - +# FIXME reimplement with env var to set this def _otlp_factory(exporter: _SpanExporterClass, options: "_Options") -> SpanExporter: return exporter(headers=(("x-sf-token", options.access_token),)) diff --git a/splunk_otel/profiling/__init__.py b/splunk_otel/profiling/__init__.py index 0f88e4e8..c26eab55 100644 --- a/splunk_otel/profiling/__init__.py +++ b/splunk_otel/profiling/__init__.py @@ -32,10 +32,10 @@ from opentelemetry.instrumentation.utils import unwrap from opentelemetry.sdk._logs import LoggerProvider, LogRecord from opentelemetry.sdk._logs.export import BatchLogRecordProcessor +from opentelemetry.sdk.resources import Resource from opentelemetry.trace import TraceFlags from opentelemetry.trace.propagation import _SPAN_KEY -from splunk_otel.options import _create_resource from splunk_otel.profiling import profile_pb2 from splunk_otel.profiling.options import _Options @@ -416,7 +416,7 @@ def start_profiling( endpoint: Optional[str] = None, call_stack_interval_millis: Optional[int] = None, ): - resource = _create_resource(service_name, resource_attributes) + resource = Resource.create({}) opts = _Options(resource, endpoint, call_stack_interval_millis) _start_profiling(opts) diff --git a/splunk_otel/tracing.py b/splunk_otel/tracing.py index 5daaa5b4..b3391b89 100644 --- a/splunk_otel/tracing.py +++ b/splunk_otel/tracing.py @@ -30,7 +30,6 @@ logger = logging.getLogger(__name__) - def start_tracing( service_name: Optional[str] = None, span_exporter_factories: Optional[Collection[_SpanExporterFactory]] = None, @@ -38,49 +37,10 @@ def start_tracing( resource_attributes: Optional[Dict[str, Union[str, bool, int, float]]] = None, trace_response_header_enabled: Optional[bool] = None, ) -> trace.TracerProvider: - enabled = os.environ.get("OTEL_TRACE_ENABLED", True) - if not _is_truthy(enabled): - logger.info("tracing has been disabled with OTEL_TRACE_ENABLED=%s", enabled) - return None - - options = _Options( - service_name, - span_exporter_factories, - access_token, - resource_attributes, - trace_response_header_enabled, - ) - try: - provider = _configure_tracing(options) - _load_instrumentors() - return provider - except Exception as error: # pylint:disable=broad-except - logger.error("tracing could not be enabled: %s", error) - return trace.NoOpTracerProvider() - - -def _configure_tracing(options: _Options) -> TracerProvider: - provider = TracerProvider(resource=options.resource) - set_global_response_propagator(options.response_propagator) # type: ignore - trace.set_tracer_provider(provider) - for factory in options.span_exporter_factories: - provider.add_span_processor(BatchSpanProcessor(factory(options))) - return provider - + # FIXME mark as deprecated or document the change or something + # FIXME document new ways to either use otel apis or ours to do same config work + # (x 5 fields) + # FIXME posibly log + return trace.get_tracer_provider() -def _load_instrumentors() -> None: - package_to_exclude = os.environ.get(OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, "").split( - "," - ) - package_to_exclude = [p.strip() for p in package_to_exclude] - for entry_point in iter_entry_points("opentelemetry_instrumentor"): - try: - if entry_point.name in package_to_exclude: - logger.debug("Instrumentation skipped for library %s", entry_point.name) - continue - entry_point.load()().instrument() - logger.debug("Instrumented %s", entry_point.name) - except Exception as exc: # pylint: disable=broad-except - logger.exception("Instrumenting of %s failed", entry_point.name) - raise exc From 1302d135bc634223e46b9529bc614a1c0413d1ba Mon Sep 17 00:00:00 2001 From: John Bley Date: Fri, 7 Jun 2024 11:16:50 -0400 Subject: [PATCH 02/30] Lint progress --- splunk_otel/distro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/splunk_otel/distro.py b/splunk_otel/distro.py index 27441f86..543eb8d9 100644 --- a/splunk_otel/distro.py +++ b/splunk_otel/distro.py @@ -17,13 +17,13 @@ from typing import Any, Dict from opentelemetry.instrumentation.distro import BaseDistro # type: ignore +from opentelemetry.sdk._configuration import _OTelSDKConfigurator from pkg_resources import EntryPoint from splunk_otel.options import _Options from splunk_otel.profiling import _start_profiling from splunk_otel.profiling.options import _Options as ProfilingOptions from splunk_otel.util import _is_truthy -from opentelemetry.sdk._configuration import _OTelSDKConfigurator logger = logging.getLogger(__name__) From 9ca61e09b2bd2dce0b7905b132e69a2b5e6b2421 Mon Sep 17 00:00:00 2001 From: John Bley Date: Fri, 7 Jun 2024 11:17:15 -0400 Subject: [PATCH 03/30] More lint/formatting --- splunk_otel/distro.py | 3 ++- splunk_otel/tracing.py | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/splunk_otel/distro.py b/splunk_otel/distro.py index 543eb8d9..c548caf2 100644 --- a/splunk_otel/distro.py +++ b/splunk_otel/distro.py @@ -27,8 +27,9 @@ logger = logging.getLogger(__name__) + class SplunkConfigurator(_OTelSDKConfigurator): - pass + pass class _SplunkDistro(BaseDistro): diff --git a/splunk_otel/tracing.py b/splunk_otel/tracing.py index b3391b89..3233553b 100644 --- a/splunk_otel/tracing.py +++ b/splunk_otel/tracing.py @@ -30,6 +30,7 @@ logger = logging.getLogger(__name__) + def start_tracing( service_name: Optional[str] = None, span_exporter_factories: Optional[Collection[_SpanExporterFactory]] = None, @@ -42,5 +43,3 @@ def start_tracing( # (x 5 fields) # FIXME posibly log return trace.get_tracer_provider() - - From 1a07340b4d0d3eab6d578e229278882aae5069a7 Mon Sep 17 00:00:00 2001 From: John Bley Date: Fri, 7 Jun 2024 11:19:04 -0400 Subject: [PATCH 04/30] More lint progress --- splunk_otel/metrics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/splunk_otel/metrics.py b/splunk_otel/metrics.py index 5413cb57..5a3c4cd5 100644 --- a/splunk_otel/metrics.py +++ b/splunk_otel/metrics.py @@ -15,12 +15,12 @@ import logging import os +from opentelemetry.metrics import get_meter_provider from opentelemetry.sdk.metrics import MeterProvider from splunk_otel.util import _is_truthy logger = logging.getLogger(__name__) -from opentelemetry.metrics import get_meter_provider def start_metrics() -> MeterProvider: From 4176077fb023d4fa63d512b4864b7d9686c78322 Mon Sep 17 00:00:00 2001 From: John Bley Date: Fri, 7 Jun 2024 11:25:49 -0400 Subject: [PATCH 05/30] lint --- splunk_otel/metrics.py | 2 -- splunk_otel/tracing.py | 16 +++++----------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/splunk_otel/metrics.py b/splunk_otel/metrics.py index 5a3c4cd5..14f6343a 100644 --- a/splunk_otel/metrics.py +++ b/splunk_otel/metrics.py @@ -13,12 +13,10 @@ # limitations under the License. import logging -import os from opentelemetry.metrics import get_meter_provider from opentelemetry.sdk.metrics import MeterProvider -from splunk_otel.util import _is_truthy logger = logging.getLogger(__name__) diff --git a/splunk_otel/tracing.py b/splunk_otel/tracing.py index 3233553b..e43220d6 100644 --- a/splunk_otel/tracing.py +++ b/splunk_otel/tracing.py @@ -13,24 +13,16 @@ # limitations under the License. import logging -import os from typing import Collection, Dict, Optional, Union from opentelemetry import trace -from opentelemetry.instrumentation.environment_variables import ( - OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, -) from opentelemetry.instrumentation.propagators import set_global_response_propagator -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor -from pkg_resources import iter_entry_points from splunk_otel.options import _Options, _SpanExporterFactory -from splunk_otel.util import _is_truthy logger = logging.getLogger(__name__) - +# pylint: disable=unused-argument def start_tracing( service_name: Optional[str] = None, span_exporter_factories: Optional[Collection[_SpanExporterFactory]] = None, @@ -39,7 +31,9 @@ def start_tracing( trace_response_header_enabled: Optional[bool] = None, ) -> trace.TracerProvider: # FIXME mark as deprecated or document the change or something - # FIXME document new ways to either use otel apis or ours to do same config work + # document new ways to either use otel apis or ours to do same config work # (x 5 fields) - # FIXME posibly log + # also posibly log + + # FIXME set_global_response_propagator needs to be called somewhere return trace.get_tracer_provider() From e65a35f84d60f73cebd485af40c77515a8a7eec1 Mon Sep 17 00:00:00 2001 From: John Bley Date: Fri, 7 Jun 2024 11:32:30 -0400 Subject: [PATCH 06/30] lint --- splunk_otel/distro.py | 3 +-- splunk_otel/options.py | 15 +-------------- splunk_otel/profiling/__init__.py | 1 + 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/splunk_otel/distro.py b/splunk_otel/distro.py index c548caf2..38821111 100644 --- a/splunk_otel/distro.py +++ b/splunk_otel/distro.py @@ -18,7 +18,6 @@ from opentelemetry.instrumentation.distro import BaseDistro # type: ignore from opentelemetry.sdk._configuration import _OTelSDKConfigurator -from pkg_resources import EntryPoint from splunk_otel.options import _Options from splunk_otel.profiling import _start_profiling @@ -42,4 +41,4 @@ def _configure(self, **kwargs: Dict[str, Any]) -> None: # FIXME _configure_Tracing and _metrics might have some unique stuff to copy thrugh to here if self._profiling_enabled: - _start_profiling(ProfilingOptions(options.resource)) + _start_profiling(ProfilingOptions()) diff --git a/splunk_otel/options.py b/splunk_otel/options.py index a3d33955..91951f42 100644 --- a/splunk_otel/options.py +++ b/splunk_otel/options.py @@ -13,11 +13,9 @@ # limitations under the License. import logging -from functools import partial from os import environ -from typing import Callable, Collection, Dict, List, Optional, Tuple, Union +from typing import Callable, Optional -from opentelemetry.environment_variables import OTEL_TRACES_EXPORTER from opentelemetry.instrumentation.propagators import ResponsePropagator from opentelemetry.instrumentation.version import ( __version__ as auto_instrumentation_version, @@ -31,9 +29,7 @@ OTEL_SPAN_EVENT_COUNT_LIMIT, OTEL_SPAN_LINK_COUNT_LIMIT, ) -from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace.export import SpanExporter -from pkg_resources import iter_entry_points from splunk_otel.environment_variables import ( _SPLUNK_ACCESS_TOKEN, @@ -41,19 +37,10 @@ ) from splunk_otel.propagators import _ServerTimingResponsePropagator from splunk_otel.symbols import ( - _DEFAULT_EXPORTERS, _DEFAULT_MAX_ATTR_LENGTH, - _DEFAULT_OTEL_SERVICE_NAME, - _DEFAULT_SERVICE_NAME, _DEFAULT_SPAN_LINK_COUNT_LIMIT, - _EXPORTER_OTLP, - _EXPORTER_OTLP_GRPC, - _KNOWN_EXPORTER_PACKAGES, _LIMIT_UNSET_VALUE, - _NO_SERVICE_NAME_WARNING, - _SERVICE_NAME_ATTR, _SPLUNK_DISTRO_VERSION_ATTR, - _TELEMETRY_VERSION_ATTR, ) from splunk_otel.util import _is_truthy_str from splunk_otel.version import __version__ diff --git a/splunk_otel/profiling/__init__.py b/splunk_otel/profiling/__init__.py index c26eab55..327e4dfa 100644 --- a/splunk_otel/profiling/__init__.py +++ b/splunk_otel/profiling/__init__.py @@ -410,6 +410,7 @@ def _start_profiling(opts): _start_profiler_thread(profiler) +# pylint: disable=unused-argument def start_profiling( service_name: Optional[str] = None, resource_attributes: Optional[Dict[str, Union[str, bool, int, float]]] = None, From f59d3f078e77a95e7fd583e052bbe3a1f9275fc4 Mon Sep 17 00:00:00 2001 From: John Bley Date: Fri, 7 Jun 2024 11:37:19 -0400 Subject: [PATCH 07/30] Removing unnecessary/duplicative tests --- tests/unit/test_metrics.py | 52 ------------------------ tests/unit/test_options.py | 82 -------------------------------------- 2 files changed, 134 deletions(-) delete mode 100644 tests/unit/test_metrics.py diff --git a/tests/unit/test_metrics.py b/tests/unit/test_metrics.py deleted file mode 100644 index 37113270..00000000 --- a/tests/unit/test_metrics.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright Splunk Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -from unittest import TestCase, mock - -from opentelemetry.metrics import NoOpMeterProvider, get_meter_provider -from opentelemetry.sdk.metrics import MeterProvider - -from splunk_otel.metrics import _configure_metrics, start_metrics -from splunk_otel.version import __version__ - -# pylint: disable=protected-access - - -class TestMetrics(TestCase): - @mock.patch.dict( - os.environ, - {"OTEL_METRICS_ENABLED": "False"}, - ) - def test_metrics_disabled(self): - meter_provider = start_metrics() - self.assertNotIsInstance(meter_provider, MeterProvider) - - @mock.patch.dict( - os.environ, - {"OTEL_METRICS_ENABLED": "True"}, - ) - def test_metrics_enabled(self): - meter_provider = start_metrics() - self.assertIsInstance(meter_provider, MeterProvider) - meter_provider.shutdown(1) - - def test_configure_metrics(self): - meter_provider = _configure_metrics() - self.assertIsInstance(meter_provider, MeterProvider) - - def test_configure_metrics_global_provider(self): - global_meter_provider = get_meter_provider() - self.assertNotIsInstance(global_meter_provider, NoOpMeterProvider) - self.assertIsInstance(global_meter_provider, MeterProvider) diff --git a/tests/unit/test_options.py b/tests/unit/test_options.py index 6eb530a6..3d98778b 100644 --- a/tests/unit/test_options.py +++ b/tests/unit/test_options.py @@ -26,88 +26,6 @@ class TestOptions(TestCase): - def test_default_service_name(self): - options = _Options() - self.assertIsInstance(options.resource, Resource) - self.assertEqual( - options.resource.attributes["service.name"], "unnamed-python-service" - ) - - def test_service_name_from_kwargs(self): - options = _Options( - resource_attributes={"service.name": "test service name from kwargs"} - ) - self.assertIsInstance(options.resource, Resource) - self.assertEqual( - options.resource.attributes["service.name"], "test service name from kwargs" - ) - - @mock.patch.dict( - os.environ, - {"OTEL_RESOURCE_ATTRIBUTES": "service.name=test service name from env"}, - ) - def test_service_name_from_env_resource_attrs(self): - options = _Options() - self.assertIsInstance(options.resource, Resource) - self.assertEqual( - options.resource.attributes["service.name"], "test service name from env" - ) - - @mock.patch.dict( - os.environ, - {"OTEL_SERVICE_NAME": "service name from otel service name env"}, - ) - def test_service_name_from_env_service_name(self): - options = _Options() - self.assertIsInstance(options.resource, Resource) - self.assertEqual( - options.resource.attributes["service.name"], - "service name from otel service name env", - ) - - @mock.patch.dict(os.environ, {"OTEL_TRACES_EXPORTER": ""}) - def test_exporters_default(self): - options = _Options() - self.assertEqual(len(options.span_exporter_factories), 1) - otlp = options.span_exporter_factories[0](options) - self.assertIsInstance(otlp, OTLPSpanExporter) - - def test_exporters_from_kwargs_classes(self): - options = _Options( - span_exporter_factories=[ - lambda opts: OTLPSpanExporter(), - lambda opts: ConsoleSpanExporter(), - ] - ) - self.assertEqual(len(options.span_exporter_factories), 2) - self.assertIsInstance( - options.span_exporter_factories[0](options), OTLPSpanExporter - ) - self.assertIsInstance( - options.span_exporter_factories[1](options), ConsoleSpanExporter - ) - - @mock.patch.dict(os.environ, {"OTEL_TRACES_EXPORTER": "otlp,console"}) - def test_exporters_from_env(self): - options = _Options() - self.assertEqual(len(options.span_exporter_factories), 2) - - otlp = options.span_exporter_factories[0](options) - self.assertIsInstance(otlp, OTLPSpanExporter) - self.assertTrue(("x-sf-token", None) in otlp._headers) - - self.assertIsInstance( - options.span_exporter_factories[1](options), ConsoleSpanExporter - ) - - @mock.patch.dict(os.environ, {"OTEL_TRACES_EXPORTER": "otlp"}) - def test_exporters_otlp_defaults(self): - options = _Options() - self.assertEqual(len(options.span_exporter_factories), 1) - factory = options.span_exporter_factories[0] - exporter = factory(options) - self.assertIsInstance(exporter, OTLPSpanExporter) - @mock.patch.dict( os.environ, { From 44d23ae8321ea48c4f56c489fd04bfd9f70b01d5 Mon Sep 17 00:00:00 2001 From: John Bley Date: Fri, 7 Jun 2024 11:40:36 -0400 Subject: [PATCH 08/30] Removing unnecessary/duplicative tests --- tests/unit/test_propagation.py | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/tests/unit/test_propagation.py b/tests/unit/test_propagation.py index 04c67761..a073ddd6 100644 --- a/tests/unit/test_propagation.py +++ b/tests/unit/test_propagation.py @@ -13,15 +13,10 @@ # limitations under the License. import os -from importlib import reload from unittest import TestCase, mock -from opentelemetry import propagate, trace -from opentelemetry.baggage.propagation import W3CBaggagePropagator +from opentelemetry import trace from opentelemetry.instrumentation.propagators import get_global_response_propagator -from opentelemetry.propagate import get_global_textmap -from opentelemetry.propagators.composite import CompositePropagator -from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator from splunk_otel.options import _Options from splunk_otel.propagators import _ServerTimingResponsePropagator @@ -29,29 +24,6 @@ class TestPropagator(TestCase): - def test_sets_tracecontext_and_baggage_are_default_propagator(self): - reload(propagate) - _configure_tracing(_Options()) - propagator = get_global_textmap() - self.assertIsInstance(propagator, CompositePropagator) - propagators = propagator._propagators # pylint: disable=protected-access - self.assertEqual(len(propagators), 2) - self.assertIsInstance(propagators[0], TraceContextTextMapPropagator) - self.assertIsInstance(propagators[1], W3CBaggagePropagator) - - @mock.patch.dict( - os.environ, - {"OTEL_PROPAGATORS": "baggage"}, - ) - def test_set_custom_propagator(self): - reload(propagate) - _configure_tracing(_Options()) - propagator = get_global_textmap() - self.assertIsInstance(propagator, CompositePropagator) - propagators = propagator._propagators # pylint: disable=protected-access - self.assertEqual(len(propagators), 1) - self.assertIsInstance(propagators[0], W3CBaggagePropagator) - def test_server_timing_is_default_response_propagator(self): _configure_tracing(_Options()) propagtor = get_global_response_propagator() From f3523c791ee875bfac79172a3e9078aa51e61317 Mon Sep 17 00:00:00 2001 From: John Bley Date: Fri, 7 Jun 2024 11:43:43 -0400 Subject: [PATCH 09/30] lint --- splunk_otel/distro.py | 4 ++-- splunk_otel/metrics.py | 1 - splunk_otel/tracing.py | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/splunk_otel/distro.py b/splunk_otel/distro.py index 38821111..1bd43c70 100644 --- a/splunk_otel/distro.py +++ b/splunk_otel/distro.py @@ -37,8 +37,8 @@ def __init__(self): self._profiling_enabled = _is_truthy(profiling_enabled) def _configure(self, **kwargs: Dict[str, Any]) -> None: - options = _Options() - # FIXME _configure_Tracing and _metrics might have some unique stuff to copy thrugh to here + # FIXME the Options construtor side effect could live here? + _Options() if self._profiling_enabled: _start_profiling(ProfilingOptions()) diff --git a/splunk_otel/metrics.py b/splunk_otel/metrics.py index 14f6343a..52092e00 100644 --- a/splunk_otel/metrics.py +++ b/splunk_otel/metrics.py @@ -17,7 +17,6 @@ from opentelemetry.metrics import get_meter_provider from opentelemetry.sdk.metrics import MeterProvider - logger = logging.getLogger(__name__) diff --git a/splunk_otel/tracing.py b/splunk_otel/tracing.py index e43220d6..344471c8 100644 --- a/splunk_otel/tracing.py +++ b/splunk_otel/tracing.py @@ -22,6 +22,7 @@ logger = logging.getLogger(__name__) + # pylint: disable=unused-argument def start_tracing( service_name: Optional[str] = None, From bc06150bfc7d38632e4eb4db913e04bb95f8f6f3 Mon Sep 17 00:00:00 2001 From: John Bley Date: Mon, 10 Jun 2024 10:25:55 -0400 Subject: [PATCH 10/30] First attempt at deprecating methods --- splunk_otel/metrics.py | 4 +++- splunk_otel/tracing.py | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/splunk_otel/metrics.py b/splunk_otel/metrics.py index 52092e00..d8e93a8f 100644 --- a/splunk_otel/metrics.py +++ b/splunk_otel/metrics.py @@ -13,6 +13,7 @@ # limitations under the License. import logging +import warnings from opentelemetry.metrics import get_meter_provider from opentelemetry.sdk.metrics import MeterProvider @@ -21,5 +22,6 @@ def start_metrics() -> MeterProvider: - # FIXME mark deprecated and/or log warning + # FIXME better message + warnings.warn("splunk_otel.start_metrics has been deprecated") return get_meter_provider() diff --git a/splunk_otel/tracing.py b/splunk_otel/tracing.py index 344471c8..94494d7f 100644 --- a/splunk_otel/tracing.py +++ b/splunk_otel/tracing.py @@ -13,6 +13,7 @@ # limitations under the License. import logging +import warnings from typing import Collection, Dict, Optional, Union from opentelemetry import trace @@ -31,10 +32,10 @@ def start_tracing( resource_attributes: Optional[Dict[str, Union[str, bool, int, float]]] = None, trace_response_header_enabled: Optional[bool] = None, ) -> trace.TracerProvider: - # FIXME mark as deprecated or document the change or something + # FIXME document the change, better message # document new ways to either use otel apis or ours to do same config work # (x 5 fields) - # also posibly log # FIXME set_global_response_propagator needs to be called somewhere + warnings.warn("splunk_otel.start_tracing has been deprecated") return trace.get_tracer_provider() From dc498f1f781cbcb23638e585ad92d2ec09cf6928 Mon Sep 17 00:00:00 2001 From: John Bley Date: Mon, 10 Jun 2024 10:40:40 -0400 Subject: [PATCH 11/30] Reimplement x-sf-token logic using upstream environment variable --- splunk_otel/distro.py | 12 ++++++++++++ splunk_otel/options.py | 5 ----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/splunk_otel/distro.py b/splunk_otel/distro.py index 1bd43c70..79aa7771 100644 --- a/splunk_otel/distro.py +++ b/splunk_otel/distro.py @@ -36,7 +36,19 @@ def __init__(self): profiling_enabled = os.environ.get("SPLUNK_PROFILER_ENABLED", False) self._profiling_enabled = _is_truthy(profiling_enabled) + def configure_access_token(self): + if "SPLUNK_ACCESS_TOKEN" in os.environ: + access_token = os.environ["SPLUNK_ACCESS_TOKEN"] + if access_token == "": + return + headers = "" + if "OTEL_EXPORTER_OTLP_HEADERS" in os.environ: + headers = os.environ["OTEL_EXPORTER_OTLP_HEADERS"] + "," + headers += "x-sf-token=" + access_token + os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = headers + def _configure(self, **kwargs: Dict[str, Any]) -> None: + self.configure_access_token() # FIXME the Options construtor side effect could live here? _Options() diff --git a/splunk_otel/options.py b/splunk_otel/options.py index 91951f42..bea48955 100644 --- a/splunk_otel/options.py +++ b/splunk_otel/options.py @@ -107,8 +107,3 @@ def _set_default_env() -> None: for key, value in defaults.items(): if key not in environ: environ[key] = value - - -# FIXME reimplement with env var to set this -def _otlp_factory(exporter: _SpanExporterClass, options: "_Options") -> SpanExporter: - return exporter(headers=(("x-sf-token", options.access_token),)) From 80d305a26f70323b932f3f00d554cfd6c35ba2fc Mon Sep 17 00:00:00 2001 From: John Bley Date: Mon, 10 Jun 2024 10:51:51 -0400 Subject: [PATCH 12/30] Implement splunk.distro.version as simple resource attribute --- splunk_otel/distro.py | 10 ++++++++++ splunk_otel/options.py | 9 --------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/splunk_otel/distro.py b/splunk_otel/distro.py index 79aa7771..fe9d6ece 100644 --- a/splunk_otel/distro.py +++ b/splunk_otel/distro.py @@ -22,7 +22,9 @@ from splunk_otel.options import _Options from splunk_otel.profiling import _start_profiling from splunk_otel.profiling.options import _Options as ProfilingOptions +from splunk_otel.symbols import _SPLUNK_DISTRO_VERSION_ATTR from splunk_otel.util import _is_truthy +from splunk_otel.version import __version__ logger = logging.getLogger(__name__) @@ -47,8 +49,16 @@ def configure_access_token(self): headers += "x-sf-token=" + access_token os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = headers + def configure_resource_attributes(self): + resource_attributes = "" + if "OTEL_RESOURCE_ATTRIBUTES" in os.environ: + resource_attributes = os.environ["OTEL_RESOURCE_ATTRIBUTES"] + "," + resource_attributes += _SPLUNK_DISTRO_VERSION_ATTR + "=" + __version__ + os.environ["OTEL_RESOURCE_ATTRIBUTES"] = resource_attributes + def _configure(self, **kwargs: Dict[str, Any]) -> None: self.configure_access_token() + self.configure_resource_attributes() # FIXME the Options construtor side effect could live here? _Options() diff --git a/splunk_otel/options.py b/splunk_otel/options.py index bea48955..17c6cb0a 100644 --- a/splunk_otel/options.py +++ b/splunk_otel/options.py @@ -17,9 +17,6 @@ from typing import Callable, Optional from opentelemetry.instrumentation.propagators import ResponsePropagator -from opentelemetry.instrumentation.version import ( - __version__ as auto_instrumentation_version, -) from opentelemetry.sdk.environment_variables import ( OTEL_ATTRIBUTE_COUNT_LIMIT, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, @@ -40,10 +37,8 @@ _DEFAULT_MAX_ATTR_LENGTH, _DEFAULT_SPAN_LINK_COUNT_LIMIT, _LIMIT_UNSET_VALUE, - _SPLUNK_DISTRO_VERSION_ATTR, ) from splunk_otel.util import _is_truthy_str -from splunk_otel.version import __version__ _SpanExporterFactory = Callable[["_Options"], SpanExporter] _SpanExporterClass = Callable[..., SpanExporter] @@ -88,10 +83,6 @@ def _get_response_propagator( return None -# FIXME need our splunk.distro.version attribute added to the resource -# _SPLUNK_DISTRO_VERSION_ATTR: __version__, - - def _set_default_env() -> None: # FIXME audit this for same-as-upstream or unique-to-us defaults = { From 0879327ba33c32da3da6784f45e5f707e5435b89 Mon Sep 17 00:00:00 2001 From: John Bley Date: Mon, 10 Jun 2024 11:12:51 -0400 Subject: [PATCH 13/30] Move and simplify otel defaults settings --- splunk_otel/distro.py | 34 +++++++++++++++++++++++++--------- splunk_otel/options.py | 34 ---------------------------------- 2 files changed, 25 insertions(+), 43 deletions(-) diff --git a/splunk_otel/distro.py b/splunk_otel/distro.py index fe9d6ece..b6140f37 100644 --- a/splunk_otel/distro.py +++ b/splunk_otel/distro.py @@ -18,11 +18,19 @@ from opentelemetry.instrumentation.distro import BaseDistro # type: ignore from opentelemetry.sdk._configuration import _OTelSDKConfigurator +from opentelemetry.sdk.environment_variables import ( + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, + OTEL_SPAN_LINK_COUNT_LIMIT, +) from splunk_otel.options import _Options -from splunk_otel.profiling import _start_profiling +from splunk_otel.profiling import start_profiling from splunk_otel.profiling.options import _Options as ProfilingOptions -from splunk_otel.symbols import _SPLUNK_DISTRO_VERSION_ATTR +from splunk_otel.symbols import ( + _DEFAULT_MAX_ATTR_LENGTH, + _DEFAULT_SPAN_LINK_COUNT_LIMIT, + _SPLUNK_DISTRO_VERSION_ATTR, +) from splunk_otel.util import _is_truthy from splunk_otel.version import __version__ @@ -38,7 +46,7 @@ def __init__(self): profiling_enabled = os.environ.get("SPLUNK_PROFILER_ENABLED", False) self._profiling_enabled = _is_truthy(profiling_enabled) - def configure_access_token(self): + def _configure_access_token(self) -> None: if "SPLUNK_ACCESS_TOKEN" in os.environ: access_token = os.environ["SPLUNK_ACCESS_TOKEN"] if access_token == "": @@ -49,18 +57,26 @@ def configure_access_token(self): headers += "x-sf-token=" + access_token os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = headers - def configure_resource_attributes(self): + def _configure_resource_attributes(self) -> None: resource_attributes = "" if "OTEL_RESOURCE_ATTRIBUTES" in os.environ: resource_attributes = os.environ["OTEL_RESOURCE_ATTRIBUTES"] + "," resource_attributes += _SPLUNK_DISTRO_VERSION_ATTR + "=" + __version__ os.environ["OTEL_RESOURCE_ATTRIBUTES"] = resource_attributes + def _set_default_env(self) -> None: + defaults = { + OTEL_SPAN_LINK_COUNT_LIMIT: str(_DEFAULT_SPAN_LINK_COUNT_LIMIT), + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT: str(_DEFAULT_MAX_ATTR_LENGTH), + } + for key, value in defaults.items(): + if key not in os.environ: + os.environ[key] = value + def _configure(self, **kwargs: Dict[str, Any]) -> None: - self.configure_access_token() - self.configure_resource_attributes() - # FIXME the Options construtor side effect could live here? - _Options() + self._set_default_env() + self._configure_access_token() + self._configure_resource_attributes() if self._profiling_enabled: - _start_profiling(ProfilingOptions()) + start_profiling() diff --git a/splunk_otel/options.py b/splunk_otel/options.py index 17c6cb0a..ddc51a83 100644 --- a/splunk_otel/options.py +++ b/splunk_otel/options.py @@ -17,15 +17,6 @@ from typing import Callable, Optional from opentelemetry.instrumentation.propagators import ResponsePropagator -from opentelemetry.sdk.environment_variables import ( - OTEL_ATTRIBUTE_COUNT_LIMIT, - OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, - OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, - OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, - OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, - OTEL_SPAN_EVENT_COUNT_LIMIT, - OTEL_SPAN_LINK_COUNT_LIMIT, -) from opentelemetry.sdk.trace.export import SpanExporter from splunk_otel.environment_variables import ( @@ -33,11 +24,6 @@ _SPLUNK_TRACE_RESPONSE_HEADER_ENABLED, ) from splunk_otel.propagators import _ServerTimingResponsePropagator -from splunk_otel.symbols import ( - _DEFAULT_MAX_ATTR_LENGTH, - _DEFAULT_SPAN_LINK_COUNT_LIMIT, - _LIMIT_UNSET_VALUE, -) from splunk_otel.util import _is_truthy_str _SpanExporterFactory = Callable[["_Options"], SpanExporter] @@ -58,9 +44,6 @@ def __init__( access_token: Optional[str] = None, trace_response_header_enabled: Optional[bool] = None, ): - # todo: remove this side effect - _set_default_env() - self.access_token = _resolve_access_token(access_token) self.response_propagator = _get_response_propagator(trace_response_header_enabled) @@ -81,20 +64,3 @@ def _get_response_propagator( if enabled: return _ServerTimingResponsePropagator() return None - - -def _set_default_env() -> None: - # FIXME audit this for same-as-upstream or unique-to-us - defaults = { - OTEL_ATTRIBUTE_COUNT_LIMIT: _LIMIT_UNSET_VALUE, - OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: _LIMIT_UNSET_VALUE, - OTEL_SPAN_EVENT_COUNT_LIMIT: _LIMIT_UNSET_VALUE, - OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT: _LIMIT_UNSET_VALUE, - OTEL_LINK_ATTRIBUTE_COUNT_LIMIT: _LIMIT_UNSET_VALUE, - OTEL_SPAN_LINK_COUNT_LIMIT: str(_DEFAULT_SPAN_LINK_COUNT_LIMIT), - OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT: str(_DEFAULT_MAX_ATTR_LENGTH), - } - - for key, value in defaults.items(): - if key not in environ: - environ[key] = value From 3f14aad9b935cffe5acdb542e3a2292f9750089a Mon Sep 17 00:00:00 2001 From: John Bley Date: Mon, 10 Jun 2024 11:51:08 -0400 Subject: [PATCH 14/30] Delete rather than deprecate unnecessary initialziation methods --- splunk_otel/__init__.py | 2 -- splunk_otel/distro.py | 12 ++++++-- splunk_otel/metrics.py | 27 ----------------- splunk_otel/options.py | 66 ----------------------------------------- splunk_otel/tracing.py | 41 ------------------------- 5 files changed, 9 insertions(+), 139 deletions(-) delete mode 100644 splunk_otel/metrics.py delete mode 100644 splunk_otel/options.py delete mode 100644 splunk_otel/tracing.py diff --git a/splunk_otel/__init__.py b/splunk_otel/__init__.py index e4e23384..9e340aa1 100644 --- a/splunk_otel/__init__.py +++ b/splunk_otel/__init__.py @@ -27,7 +27,5 @@ _set_otel_defaults() # pylint: disable=wrong-import-position -from .metrics import start_metrics # noqa: E402 from .profiling import start_profiling # noqa: E402 -from .tracing import start_tracing # noqa: E402 from .version import __version__ # noqa: E402 diff --git a/splunk_otel/distro.py b/splunk_otel/distro.py index b6140f37..6a8d342c 100644 --- a/splunk_otel/distro.py +++ b/splunk_otel/distro.py @@ -17,21 +17,22 @@ from typing import Any, Dict from opentelemetry.instrumentation.distro import BaseDistro # type: ignore +from opentelemetry.instrumentation.propagators import set_global_response_propagator from opentelemetry.sdk._configuration import _OTelSDKConfigurator from opentelemetry.sdk.environment_variables import ( OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, OTEL_SPAN_LINK_COUNT_LIMIT, ) -from splunk_otel.options import _Options +from splunk_otel.environment_variables import _SPLUNK_TRACE_RESPONSE_HEADER_ENABLED from splunk_otel.profiling import start_profiling -from splunk_otel.profiling.options import _Options as ProfilingOptions +from splunk_otel.propagators import _ServerTimingResponsePropagator from splunk_otel.symbols import ( _DEFAULT_MAX_ATTR_LENGTH, _DEFAULT_SPAN_LINK_COUNT_LIMIT, _SPLUNK_DISTRO_VERSION_ATTR, ) -from splunk_otel.util import _is_truthy +from splunk_otel.util import _is_truthy, _is_truthy_str from splunk_otel.version import __version__ logger = logging.getLogger(__name__) @@ -73,10 +74,15 @@ def _set_default_env(self) -> None: if key not in os.environ: os.environ[key] = value + def _set_server_timing_propagator(self) -> None: + if _is_truthy_str(os.environ.get(_SPLUNK_TRACE_RESPONSE_HEADER_ENABLED, "true")): + set_global_response_propagator(_ServerTimingResponsePropagator()) + def _configure(self, **kwargs: Dict[str, Any]) -> None: self._set_default_env() self._configure_access_token() self._configure_resource_attributes() + self._set_server_timing_propagator() if self._profiling_enabled: start_profiling() diff --git a/splunk_otel/metrics.py b/splunk_otel/metrics.py deleted file mode 100644 index d8e93a8f..00000000 --- a/splunk_otel/metrics.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright Splunk Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -import warnings - -from opentelemetry.metrics import get_meter_provider -from opentelemetry.sdk.metrics import MeterProvider - -logger = logging.getLogger(__name__) - - -def start_metrics() -> MeterProvider: - # FIXME better message - warnings.warn("splunk_otel.start_metrics has been deprecated") - return get_meter_provider() diff --git a/splunk_otel/options.py b/splunk_otel/options.py deleted file mode 100644 index ddc51a83..00000000 --- a/splunk_otel/options.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright Splunk Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -from os import environ -from typing import Callable, Optional - -from opentelemetry.instrumentation.propagators import ResponsePropagator -from opentelemetry.sdk.trace.export import SpanExporter - -from splunk_otel.environment_variables import ( - _SPLUNK_ACCESS_TOKEN, - _SPLUNK_TRACE_RESPONSE_HEADER_ENABLED, -) -from splunk_otel.propagators import _ServerTimingResponsePropagator -from splunk_otel.util import _is_truthy_str - -_SpanExporterFactory = Callable[["_Options"], SpanExporter] -_SpanExporterClass = Callable[..., SpanExporter] - -logger = logging.getLogger("options") - - -# FIXME possibly deal with one-off customer issues with documenting how to customize -# span exporters with stock otel apis -class _Options: - access_token: Optional[str] - response_propagation: bool - response_propagator: Optional[ResponsePropagator] - - def __init__( - self, - access_token: Optional[str] = None, - trace_response_header_enabled: Optional[bool] = None, - ): - self.access_token = _resolve_access_token(access_token) - self.response_propagator = _get_response_propagator(trace_response_header_enabled) - - -def _resolve_access_token(access_token: Optional[str]) -> Optional[str]: - if not access_token: - access_token = environ.get(_SPLUNK_ACCESS_TOKEN) - return access_token or None - - -def _get_response_propagator( - enabled: Optional[bool], -) -> Optional[ResponsePropagator]: - if enabled is None: - enabled = _is_truthy_str( - environ.get(_SPLUNK_TRACE_RESPONSE_HEADER_ENABLED, "true") - ) - if enabled: - return _ServerTimingResponsePropagator() - return None diff --git a/splunk_otel/tracing.py b/splunk_otel/tracing.py deleted file mode 100644 index 94494d7f..00000000 --- a/splunk_otel/tracing.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright Splunk Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -import warnings -from typing import Collection, Dict, Optional, Union - -from opentelemetry import trace -from opentelemetry.instrumentation.propagators import set_global_response_propagator - -from splunk_otel.options import _Options, _SpanExporterFactory - -logger = logging.getLogger(__name__) - - -# pylint: disable=unused-argument -def start_tracing( - service_name: Optional[str] = None, - span_exporter_factories: Optional[Collection[_SpanExporterFactory]] = None, - access_token: Optional[str] = None, - resource_attributes: Optional[Dict[str, Union[str, bool, int, float]]] = None, - trace_response_header_enabled: Optional[bool] = None, -) -> trace.TracerProvider: - # FIXME document the change, better message - # document new ways to either use otel apis or ours to do same config work - # (x 5 fields) - - # FIXME set_global_response_propagator needs to be called somewhere - warnings.warn("splunk_otel.start_tracing has been deprecated") - return trace.get_tracer_provider() From 662bd45b46cfbcd52ad7c94b5f0d68534cbc44b4 Mon Sep 17 00:00:00 2001 From: John Bley Date: Mon, 10 Jun 2024 12:13:01 -0400 Subject: [PATCH 15/30] Remove OTEL_PYTHON_LOG_CORRELATION=true as a default --- splunk_otel/__init__.py | 3 --- splunk_otel/defaults.py | 23 ----------------------- 2 files changed, 26 deletions(-) delete mode 100644 splunk_otel/defaults.py diff --git a/splunk_otel/__init__.py b/splunk_otel/__init__.py index 9e340aa1..bd70c8b4 100644 --- a/splunk_otel/__init__.py +++ b/splunk_otel/__init__.py @@ -19,13 +19,10 @@ https://github.com/signalfx/splunk-otel-python """ -from .defaults import _set_otel_defaults from .util import _init_logger _init_logger("splunk_otel") -_set_otel_defaults() - # pylint: disable=wrong-import-position from .profiling import start_profiling # noqa: E402 from .version import __version__ # noqa: E402 diff --git a/splunk_otel/defaults.py b/splunk_otel/defaults.py deleted file mode 100644 index e99891ca..00000000 --- a/splunk_otel/defaults.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright Splunk Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from os import environ - -# FIXME remove this -_OTEL_PYTHON_LOG_CORRELATION = "OTEL_PYTHON_LOG_CORRELATION" - - -def _set_otel_defaults() -> None: - if _OTEL_PYTHON_LOG_CORRELATION not in environ: - environ[_OTEL_PYTHON_LOG_CORRELATION] = "true" From 1d8163e99116421dc160f39d95363f9db31d0067 Mon Sep 17 00:00:00 2001 From: John Bley Date: Mon, 10 Jun 2024 12:15:15 -0400 Subject: [PATCH 16/30] Change version to 2.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c0dfed2d..ac9621f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "splunk-opentelemetry" -version = "1.19.1" +version = "2.0" description = "The Splunk distribution of OpenTelemetry Python Instrumentation provides a Python agent that automatically instruments your Python application to capture and report distributed traces to SignalFx APM." authors = ["Splunk "] license = "Apache-2.0" From 0d5c7280146aed81d0cebd225460a0b001829215 Mon Sep 17 00:00:00 2001 From: Pablo Collins Date: Tue, 2 Jul 2024 07:56:58 -0400 Subject: [PATCH 17/30] add tests and remove old ones (#470) --- splunk_otel/distro.py | 76 ++++++++++++++++++-------------- tests/unit/__init__.py | 0 tests/unit/test_distro.py | 46 +++++++++++++++++++ tests/unit/test_options.py | 52 ---------------------- tests/unit/test_otel_defaults.py | 44 ------------------ 5 files changed, 88 insertions(+), 130 deletions(-) delete mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/test_distro.py delete mode 100644 tests/unit/test_options.py delete mode 100644 tests/unit/test_otel_defaults.py diff --git a/splunk_otel/distro.py b/splunk_otel/distro.py index 6a8d342c..dede71b0 100644 --- a/splunk_otel/distro.py +++ b/splunk_otel/distro.py @@ -14,13 +14,16 @@ import logging import os -from typing import Any, Dict +from opentelemetry.environment_variables import OTEL_METRICS_EXPORTER, OTEL_TRACES_EXPORTER from opentelemetry.instrumentation.distro import BaseDistro # type: ignore from opentelemetry.instrumentation.propagators import set_global_response_propagator from opentelemetry.sdk._configuration import _OTelSDKConfigurator from opentelemetry.sdk.environment_variables import ( - OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, + OTEL_ATTRIBUTE_COUNT_LIMIT, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, + OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, + OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, + OTEL_SPAN_EVENT_COUNT_LIMIT, OTEL_SPAN_LINK_COUNT_LIMIT, ) @@ -39,24 +42,47 @@ class SplunkConfigurator(_OTelSDKConfigurator): - pass + + def _configure(self, **kwargs): + super()._configure(**kwargs) + if _is_truthy(os.environ.get("SPLUNK_PROFILER_ENABLED", False)): + start_profiling() class _SplunkDistro(BaseDistro): - def __init__(self): - profiling_enabled = os.environ.get("SPLUNK_PROFILER_ENABLED", False) - self._profiling_enabled = _is_truthy(profiling_enabled) - def _configure_access_token(self) -> None: - if "SPLUNK_ACCESS_TOKEN" in os.environ: - access_token = os.environ["SPLUNK_ACCESS_TOKEN"] - if access_token == "": - return - headers = "" - if "OTEL_EXPORTER_OTLP_HEADERS" in os.environ: - headers = os.environ["OTEL_EXPORTER_OTLP_HEADERS"] + "," - headers += "x-sf-token=" + access_token - os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = headers + def _configure(self, **kwargs): + # this runs *before* the configurator + self._set_env() + self._configure_headers() + self._configure_resource_attributes() + self._set_server_timing_propagator() + + def _set_env(self): + os.environ.setdefault("OTEL_PYTHON_LOG_CORRELATION", "true") + os.environ.setdefault(OTEL_TRACES_EXPORTER, "otlp") + os.environ.setdefault(OTEL_METRICS_EXPORTER, "otlp") + os.environ.setdefault(OTEL_EXPORTER_OTLP_PROTOCOL, "grpc") + os.environ.setdefault(OTEL_ATTRIBUTE_COUNT_LIMIT, "") + os.environ.setdefault(OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, "") + os.environ.setdefault(OTEL_SPAN_EVENT_COUNT_LIMIT, "") + os.environ.setdefault(OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, "") + os.environ.setdefault(OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, "") + os.environ.setdefault(OTEL_SPAN_LINK_COUNT_LIMIT, str(_DEFAULT_SPAN_LINK_COUNT_LIMIT)) + os.environ.setdefault(OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, str(_DEFAULT_MAX_ATTR_LENGTH)) + + def _configure_headers(self) -> None: + if "SPLUNK_ACCESS_TOKEN" not in os.environ: + return + + access_token = os.environ["SPLUNK_ACCESS_TOKEN"] + if access_token == "": + return + headers = "" + if "OTEL_EXPORTER_OTLP_HEADERS" in os.environ: + headers = os.environ["OTEL_EXPORTER_OTLP_HEADERS"] + "," + headers += "x-sf-token=" + access_token + os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = headers def _configure_resource_attributes(self) -> None: resource_attributes = "" @@ -65,24 +91,6 @@ def _configure_resource_attributes(self) -> None: resource_attributes += _SPLUNK_DISTRO_VERSION_ATTR + "=" + __version__ os.environ["OTEL_RESOURCE_ATTRIBUTES"] = resource_attributes - def _set_default_env(self) -> None: - defaults = { - OTEL_SPAN_LINK_COUNT_LIMIT: str(_DEFAULT_SPAN_LINK_COUNT_LIMIT), - OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT: str(_DEFAULT_MAX_ATTR_LENGTH), - } - for key, value in defaults.items(): - if key not in os.environ: - os.environ[key] = value - def _set_server_timing_propagator(self) -> None: if _is_truthy_str(os.environ.get(_SPLUNK_TRACE_RESPONSE_HEADER_ENABLED, "true")): set_global_response_propagator(_ServerTimingResponsePropagator()) - - def _configure(self, **kwargs: Dict[str, Any]) -> None: - self._set_default_env() - self._configure_access_token() - self._configure_resource_attributes() - self._set_server_timing_propagator() - - if self._profiling_enabled: - start_profiling() diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/unit/test_distro.py b/tests/unit/test_distro.py new file mode 100644 index 00000000..62b926cf --- /dev/null +++ b/tests/unit/test_distro.py @@ -0,0 +1,46 @@ +# Copyright Splunk Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os + +from opentelemetry.instrumentation.propagators import get_global_response_propagator +from opentelemetry.sdk.trace import SpanLimits, TracerProvider + +from splunk_otel.distro import _SplunkDistro +from splunk_otel.propagators import _ServerTimingResponsePropagator + + +def test_distro_env(monkeypatch): + monkeypatch.setenv("SPLUNK_ACCESS_TOKEN", "s4cr4t") + sd = _SplunkDistro() + sd.configure() + assert os.getenv("OTEL_PYTHON_LOG_CORRELATION") == "true" + assert os.getenv("OTEL_TRACES_EXPORTER") == "otlp" + assert os.getenv("OTEL_METRICS_EXPORTER") == "otlp" + assert os.getenv("OTEL_EXPORTER_OTLP_PROTOCOL") == "grpc" + assert os.getenv("OTEL_EXPORTER_OTLP_HEADERS") == "x-sf-token=s4cr4t" + assert os.getenv("OTEL_RESOURCE_ATTRIBUTES") == "splunk.distro.version=2.0" + assert type(get_global_response_propagator()) is _ServerTimingResponsePropagator + + +def test_default_limits(): + _SplunkDistro().configure() + limits = SpanLimits() + assert limits.max_events is None + assert limits.max_span_attributes is None + assert limits.max_event_attributes is None + assert limits.max_link_attributes is None + + assert limits.max_links == 1000 + assert limits.max_attribute_length == 12000 + assert limits.max_span_attribute_length == 12000 diff --git a/tests/unit/test_options.py b/tests/unit/test_options.py deleted file mode 100644 index 3d98778b..00000000 --- a/tests/unit/test_options.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright Splunk Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -from unittest import TestCase, mock - -from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter -from opentelemetry.sdk.resources import Resource -from opentelemetry.sdk.trace.export import ConsoleSpanExporter - -from splunk_otel.options import _Options -from splunk_otel.version import __version__ - -# pylint: disable=protected-access - - -class TestOptions(TestCase): - @mock.patch.dict( - os.environ, - { - "OTEL_TRACES_EXPORTER": "otlp", - "SPLUNK_MAX_ATTR_LENGTH": "10", - "SPLUNK_ACCESS_TOKEN": "12345", - }, - ) - def test_exporters_otlp_custom(self): - options = _Options() - self.assertEqual(len(options.span_exporter_factories), 1) - - factory = options.span_exporter_factories[0] - exporter = factory(options) - self.assertIsInstance(exporter, OTLPSpanExporter) - self.assertTrue(("x-sf-token", "12345") in exporter._headers) - - def test_telemetry_attributes(self): - options = _Options() - self.assertIsInstance(options.resource, Resource) - self.assertEqual( - options.resource.attributes["splunk.distro.version"], - __version__, - ) diff --git a/tests/unit/test_otel_defaults.py b/tests/unit/test_otel_defaults.py deleted file mode 100644 index 64a84b95..00000000 --- a/tests/unit/test_otel_defaults.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright Splunk Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest -from os import environ - -from opentelemetry.sdk.trace import TracerProvider - -from splunk_otel.defaults import _OTEL_PYTHON_LOG_CORRELATION -from splunk_otel.options import _Options - - -class TestOtelDefaults(unittest.TestCase): - # pylint: disable=protected-access,import-outside-toplevel - - def test_default_limits(self): - # instantiating _Options() sets default env vars - _Options() - limits = TracerProvider()._span_limits - - # unlimited by default - self.assertIsNone(limits.max_events) - self.assertIsNone(limits.max_span_attributes) - self.assertIsNone(limits.max_event_attributes) - self.assertIsNone(limits.max_link_attributes) - - # have default limits - self.assertEqual(limits.max_links, 1000) - self.assertEqual(limits.max_attribute_length, 12000) - self.assertEqual(limits.max_span_attribute_length, 12000) - - def test_otel_log_correlation_enabled(self): - self.assertTrue(environ[_OTEL_PYTHON_LOG_CORRELATION]) From 619913f1cdfaede5c59fa304f6515cf725bbad9e Mon Sep 17 00:00:00 2001 From: John Bley Date: Tue, 2 Jul 2024 08:38:22 -0400 Subject: [PATCH 18/30] Drop outdated propagation config test, keep propagation logic test --- tests/unit/test_propagation.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/tests/unit/test_propagation.py b/tests/unit/test_propagation.py index a073ddd6..f5c41f05 100644 --- a/tests/unit/test_propagation.py +++ b/tests/unit/test_propagation.py @@ -18,29 +18,7 @@ from opentelemetry import trace from opentelemetry.instrumentation.propagators import get_global_response_propagator -from splunk_otel.options import _Options from splunk_otel.propagators import _ServerTimingResponsePropagator -from splunk_otel.tracing import _configure_tracing - - -class TestPropagator(TestCase): - def test_server_timing_is_default_response_propagator(self): - _configure_tracing(_Options()) - propagtor = get_global_response_propagator() - self.assertIsInstance(propagtor, _ServerTimingResponsePropagator) - - def test_server_timing_is_global_response_propagator_disabled_code(self): - _configure_tracing(_Options(trace_response_header_enabled=False)) - self.assertIsNone(get_global_response_propagator()) - - @mock.patch.dict( - os.environ, - {"SPLUNK_TRACE_RESPONSE_HEADER_ENABLED": "false"}, - ) - def test_server_timing_is_global_response_propagator_disabled_env(self): - _configure_tracing(_Options()) - self.assertIsNone(get_global_response_propagator()) - class TestServerTimingResponsePropagator(TestCase): def test_inject(self): From 7e88ec0e6bc92ff7c1a42466685fddc4765fda3e Mon Sep 17 00:00:00 2001 From: John Bley Date: Tue, 2 Jul 2024 08:53:21 -0400 Subject: [PATCH 19/30] Remove service/resource stuff from profiling api, adjust tests --- splunk_otel/profiling/__init__.py | 2 -- tests/unit/test_profiling.py | 6 ------ 2 files changed, 8 deletions(-) diff --git a/splunk_otel/profiling/__init__.py b/splunk_otel/profiling/__init__.py index 327e4dfa..cc3ef729 100644 --- a/splunk_otel/profiling/__init__.py +++ b/splunk_otel/profiling/__init__.py @@ -412,8 +412,6 @@ def _start_profiling(opts): # pylint: disable=unused-argument def start_profiling( - service_name: Optional[str] = None, - resource_attributes: Optional[Dict[str, Union[str, bool, int, float]]] = None, endpoint: Optional[str] = None, call_stack_interval_millis: Optional[int] = None, ): diff --git a/tests/unit/test_profiling.py b/tests/unit/test_profiling.py index 5683c479..e1e2348d 100644 --- a/tests/unit/test_profiling.py +++ b/tests/unit/test_profiling.py @@ -93,7 +93,6 @@ def tearDown(self): def profile_capture_thread_ids(self): start_profiling( - service_name="prof-thread-filter", call_stack_interval_millis=10, ) @@ -135,9 +134,7 @@ def _assert_log_record(self, log_record): resource = log_record.resource.attributes - self.assertEqual(resource["foo"], "bar") self.assertEqual(resource["telemetry.sdk.language"], "python") - self.assertEqual(resource["service.name"], "prof-export-test") profile = log_record_to_profile(log_record) @@ -181,8 +178,6 @@ def test_profiling_export(self): tracer = trace_api.get_tracer("tests.tracer") start_profiling( - service_name="prof-export-test", - resource_attributes={"foo": "bar"}, call_stack_interval_millis=100, ) @@ -235,7 +230,6 @@ def test_non_recording_span(self): tracer = provider.get_tracer("tests.tracer") start_profiling( - service_name="nonrecording span test", call_stack_interval_millis=100, ) From 2ce2b853d85c316a93940b7d9227f87a7ab80200 Mon Sep 17 00:00:00 2001 From: John Bley Date: Tue, 2 Jul 2024 08:58:24 -0400 Subject: [PATCH 20/30] Try out pip upgrade that might work for windows? --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d28cefc6..59042886 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,8 @@ install-tox: .PHONY: install-setuptools install-setuptools: - pip install --upgrade pip setuptools wheel + python -m pip install --upgrade pip + pip install --upgrade setuptools wheel .PHONY: install-tools install-tools: install-setuptools install-poetry install-tox From b8cdbd9ae0b923319520d14118e3224598d8a732 Mon Sep 17 00:00:00 2001 From: John Bley Date: Tue, 2 Jul 2024 09:06:33 -0400 Subject: [PATCH 21/30] Linting --- splunk_otel/distro.py | 20 +++++++++++++++----- tests/unit/test_propagation.py | 1 + 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/splunk_otel/distro.py b/splunk_otel/distro.py index dede71b0..7fee454c 100644 --- a/splunk_otel/distro.py +++ b/splunk_otel/distro.py @@ -15,13 +15,19 @@ import logging import os -from opentelemetry.environment_variables import OTEL_METRICS_EXPORTER, OTEL_TRACES_EXPORTER +from opentelemetry.environment_variables import ( + OTEL_METRICS_EXPORTER, + OTEL_TRACES_EXPORTER, +) from opentelemetry.instrumentation.distro import BaseDistro # type: ignore from opentelemetry.instrumentation.propagators import set_global_response_propagator from opentelemetry.sdk._configuration import _OTelSDKConfigurator from opentelemetry.sdk.environment_variables import ( - OTEL_ATTRIBUTE_COUNT_LIMIT, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, - OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, + OTEL_ATTRIBUTE_COUNT_LIMIT, + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, + OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, + OTEL_EXPORTER_OTLP_PROTOCOL, + OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, OTEL_SPAN_EVENT_COUNT_LIMIT, OTEL_SPAN_LINK_COUNT_LIMIT, @@ -68,8 +74,12 @@ def _set_env(self): os.environ.setdefault(OTEL_SPAN_EVENT_COUNT_LIMIT, "") os.environ.setdefault(OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, "") os.environ.setdefault(OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, "") - os.environ.setdefault(OTEL_SPAN_LINK_COUNT_LIMIT, str(_DEFAULT_SPAN_LINK_COUNT_LIMIT)) - os.environ.setdefault(OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, str(_DEFAULT_MAX_ATTR_LENGTH)) + os.environ.setdefault( + OTEL_SPAN_LINK_COUNT_LIMIT, str(_DEFAULT_SPAN_LINK_COUNT_LIMIT) + ) + os.environ.setdefault( + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, str(_DEFAULT_MAX_ATTR_LENGTH) + ) def _configure_headers(self) -> None: if "SPLUNK_ACCESS_TOKEN" not in os.environ: diff --git a/tests/unit/test_propagation.py b/tests/unit/test_propagation.py index f5c41f05..459d820a 100644 --- a/tests/unit/test_propagation.py +++ b/tests/unit/test_propagation.py @@ -20,6 +20,7 @@ from splunk_otel.propagators import _ServerTimingResponsePropagator + class TestServerTimingResponsePropagator(TestCase): def test_inject(self): span = trace.NonRecordingSpan( From 4dd654f78473559c0abcd7144e742892d791a86d Mon Sep 17 00:00:00 2001 From: John Bley Date: Tue, 2 Jul 2024 09:09:51 -0400 Subject: [PATCH 22/30] more linting --- tests/unit/test_distro.py | 22 +++++++++++----------- tests/unit/test_propagation.py | 4 +--- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/tests/unit/test_distro.py b/tests/unit/test_distro.py index 62b926cf..d456e783 100644 --- a/tests/unit/test_distro.py +++ b/tests/unit/test_distro.py @@ -1,20 +1,20 @@ -# Copyright Splunk Inc. +# Copyright Splunk Inc. # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import os from opentelemetry.instrumentation.propagators import get_global_response_propagator -from opentelemetry.sdk.trace import SpanLimits, TracerProvider +from opentelemetry.sdk.trace import SpanLimits from splunk_otel.distro import _SplunkDistro from splunk_otel.propagators import _ServerTimingResponsePropagator diff --git a/tests/unit/test_propagation.py b/tests/unit/test_propagation.py index 459d820a..7283559e 100644 --- a/tests/unit/test_propagation.py +++ b/tests/unit/test_propagation.py @@ -12,11 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os -from unittest import TestCase, mock +from unittest import TestCase from opentelemetry import trace -from opentelemetry.instrumentation.propagators import get_global_response_propagator from splunk_otel.propagators import _ServerTimingResponsePropagator From f587d48ec95878200f5680c305f09550da78a255 Mon Sep 17 00:00:00 2001 From: John Bley Date: Tue, 2 Jul 2024 09:11:38 -0400 Subject: [PATCH 23/30] yet more linting --- tests/unit/test_distro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_distro.py b/tests/unit/test_distro.py index d456e783..cd1c7f36 100644 --- a/tests/unit/test_distro.py +++ b/tests/unit/test_distro.py @@ -30,7 +30,7 @@ def test_distro_env(monkeypatch): assert os.getenv("OTEL_EXPORTER_OTLP_PROTOCOL") == "grpc" assert os.getenv("OTEL_EXPORTER_OTLP_HEADERS") == "x-sf-token=s4cr4t" assert os.getenv("OTEL_RESOURCE_ATTRIBUTES") == "splunk.distro.version=2.0" - assert type(get_global_response_propagator()) is _ServerTimingResponsePropagator + assert isinstance(get_global_response_propagator(), _ServerTimingResponsePropagator) def test_default_limits(): From 0bc54d3a907af038ea0fdd5659f17bc745dd1a76 Mon Sep 17 00:00:00 2001 From: John Bley Date: Tue, 2 Jul 2024 09:21:00 -0400 Subject: [PATCH 24/30] Fix lint in integration tests too --- tests/integration/simple/main.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/integration/simple/main.py b/tests/integration/simple/main.py index d89b5585..446f021f 100644 --- a/tests/integration/simple/main.py +++ b/tests/integration/simple/main.py @@ -14,10 +14,6 @@ from opentelemetry import trace -from splunk_otel import start_tracing - -provider = start_tracing() - tracer = trace.get_tracer("simple", "0.1") @@ -30,5 +26,3 @@ def main(): if __name__ == "__main__": main() - provider.force_flush() - provider.shutdown() From a2817e57597ce6780550135ef5042ea7f71fed57 Mon Sep 17 00:00:00 2001 From: John Bley Date: Tue, 2 Jul 2024 17:01:25 -0400 Subject: [PATCH 25/30] use splunk-py-trace for itegration test --- tests/integration/collector.yml | 2 +- tests/integration/test_simple.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/integration/collector.yml b/tests/integration/collector.yml index bd725304..4622760a 100644 --- a/tests/integration/collector.yml +++ b/tests/integration/collector.yml @@ -7,7 +7,7 @@ receivers: grpc: endpoint: 0.0.0.0:4317 http: - endpoint: 0.0.0.0:55681 + endpoint: 0.0.0.0:4318 smartagent/signalfx-forwarder: listenAddress: '0.0.0.0:9080' diff --git a/tests/integration/test_simple.py b/tests/integration/test_simple.py index d09d7663..6d489c15 100644 --- a/tests/integration/test_simple.py +++ b/tests/integration/test_simple.py @@ -27,11 +27,8 @@ def test_otlp_simple(integration: IntegrationSession): # execute instrumented program env = os.environ.copy() - env["OTEL_TRACES_EXPORTER"] = "otlp" subprocess.check_call( - ["python", f"{integration.rootdir}/simple/main.py"], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, + ["splunk-py-trace", "python", f"{integration.rootdir}/simple/main.py"], env=env, ) From 6fb486e7efdeb0b859d3d03b36db900af0bd376f Mon Sep 17 00:00:00 2001 From: John Bley Date: Tue, 2 Jul 2024 17:04:15 -0400 Subject: [PATCH 26/30] Add brief line in CHANGELOG that will need to be expanded in future --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8025e9b3..dbca4414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +- FIXME major backwards-incompatible overhaul of initialization, apis, etc. ## 1.19.1 - 2024-05-29 - Fixed an issue with metrics exporting in a lambda environment From 8db91dd028358c7efe82bb1be70cd225baba7fd0 Mon Sep 17 00:00:00 2001 From: John Bley Date: Wed, 3 Jul 2024 09:57:59 -0400 Subject: [PATCH 27/30] Clean up handling of default environment variables --- splunk_otel/distro.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/splunk_otel/distro.py b/splunk_otel/distro.py index 7fee454c..99d6ec8f 100644 --- a/splunk_otel/distro.py +++ b/splunk_otel/distro.py @@ -65,10 +65,10 @@ def _configure(self, **kwargs): self._set_server_timing_propagator() def _set_env(self): - os.environ.setdefault("OTEL_PYTHON_LOG_CORRELATION", "true") + os.environ.setdefault("OTEL_PYTHON_LOG_CORRELATION", "true") # FIXME revisit os.environ.setdefault(OTEL_TRACES_EXPORTER, "otlp") os.environ.setdefault(OTEL_METRICS_EXPORTER, "otlp") - os.environ.setdefault(OTEL_EXPORTER_OTLP_PROTOCOL, "grpc") + os.environ.setdefault(OTEL_EXPORTER_OTLP_PROTOCOL, "grpc") # FIXME revisit os.environ.setdefault(OTEL_ATTRIBUTE_COUNT_LIMIT, "") os.environ.setdefault(OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, "") os.environ.setdefault(OTEL_SPAN_EVENT_COUNT_LIMIT, "") @@ -82,10 +82,7 @@ def _set_env(self): ) def _configure_headers(self) -> None: - if "SPLUNK_ACCESS_TOKEN" not in os.environ: - return - - access_token = os.environ["SPLUNK_ACCESS_TOKEN"] + access_token = os.environ.get("SPLUNK_ACCESS_TOKEN", "") if access_token == "": return headers = "" From 8c3bedc766e2c6ce7a5d64b6cc840d75760bbc88 Mon Sep 17 00:00:00 2001 From: John Bley Date: Wed, 3 Jul 2024 10:06:18 -0400 Subject: [PATCH 28/30] document make develop properly --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 59042886..4142d10b 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ clean: .PHONY: develop develop: ifeq ($(DEV_VENV),"") - @echo "Usage: make develop DEV_ENV=~/path/to/dev/project/venv" + @echo "Usage: make develop DEV_VENV=~/path/to/dev/project/venv" else $(DEV_VENV)/bin/pip uninstall splunk-opentelemetry --yes . $(DEV_VENV)/bin/activate && poetry install --no-dev --extras all From 783d149ee71f66c4a91f192d8ffc6cfbc6fcab34 Mon Sep 17 00:00:00 2001 From: Pablo Collins Date: Wed, 10 Jul 2024 18:55:27 -0400 Subject: [PATCH 29/30] add unit tests and remove integration tests --- poetry.lock | 2 +- splunk_otel/distro.py | 73 ++++++++++++++++++---------- splunk_otel/environment_variables.py | 16 ------ splunk_otel/symbols.py | 36 -------------- tests/integration/__init__.py | 0 tests/integration/collector.yml | 27 ---------- tests/integration/conftest.py | 60 ----------------------- tests/integration/docker-compose.yml | 15 ------ tests/integration/simple/main.py | 28 ----------- tests/integration/test_simple.py | 49 ------------------- tests/unit/test_distro.py | 44 ++++++++++++++--- 11 files changed, 85 insertions(+), 265 deletions(-) delete mode 100644 splunk_otel/environment_variables.py delete mode 100644 splunk_otel/symbols.py delete mode 100644 tests/integration/__init__.py delete mode 100644 tests/integration/collector.yml delete mode 100644 tests/integration/conftest.py delete mode 100644 tests/integration/docker-compose.yml delete mode 100644 tests/integration/simple/main.py delete mode 100644 tests/integration/test_simple.py diff --git a/poetry.lock b/poetry.lock index 75ab74bd..574def9f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "astroid" diff --git a/splunk_otel/distro.py b/splunk_otel/distro.py index 99d6ec8f..f21bda4e 100644 --- a/splunk_otel/distro.py +++ b/splunk_otel/distro.py @@ -26,39 +26,42 @@ OTEL_ATTRIBUTE_COUNT_LIMIT, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, - OTEL_EXPORTER_OTLP_PROTOCOL, + OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, - OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, + OTEL_RESOURCE_ATTRIBUTES, OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, OTEL_SPAN_EVENT_COUNT_LIMIT, OTEL_SPAN_LINK_COUNT_LIMIT, ) -from splunk_otel.environment_variables import _SPLUNK_TRACE_RESPONSE_HEADER_ENABLED from splunk_otel.profiling import start_profiling from splunk_otel.propagators import _ServerTimingResponsePropagator -from splunk_otel.symbols import ( - _DEFAULT_MAX_ATTR_LENGTH, - _DEFAULT_SPAN_LINK_COUNT_LIMIT, - _SPLUNK_DISTRO_VERSION_ATTR, -) from splunk_otel.util import _is_truthy, _is_truthy_str from splunk_otel.version import __version__ -logger = logging.getLogger(__name__) - +DEFAULT_MAX_ATTR_LENGTH = 12000 +DEFAULT_SPAN_LINK_COUNT_LIMIT = 1000 +SPLUNK_DISTRO_VERSION_ATTR = "splunk.distro.version" -class SplunkConfigurator(_OTelSDKConfigurator): +logger = logging.getLogger(__name__) - def _configure(self, **kwargs): - super()._configure(**kwargs) - if _is_truthy(os.environ.get("SPLUNK_PROFILER_ENABLED", False)): - start_profiling() +# The distro, configurators, and instrumentors that have been registered via entrypoints +# are loaded by opentelemetry-instrumentation (contrib) via sitecustomize.py: +# +# distro = _load_distro() # loads entrypoints "opentelemetry_distro" +# distro.configure() # calls _configure() below +# _load_configurators() # loads entrypoints "opentelemetry_configurator" +# _load_instrumentors(distro) # loads entrypoints "opentelemetry_instrumentor" +class SplunkDistro(BaseDistro): + """ + This class is referenced in the local pyproject.toml: -class _SplunkDistro(BaseDistro): + [tool.poetry.plugins."opentelemetry_distro"] + splunk_distro = "splunk_otel.distro:SplunkDistro" + """ def _configure(self, **kwargs): - # this runs *before* the configurator + # runs *before* the configurator self._set_env() self._configure_headers() self._configure_resource_attributes() @@ -75,10 +78,10 @@ def _set_env(self): os.environ.setdefault(OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, "") os.environ.setdefault(OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, "") os.environ.setdefault( - OTEL_SPAN_LINK_COUNT_LIMIT, str(_DEFAULT_SPAN_LINK_COUNT_LIMIT) + OTEL_SPAN_LINK_COUNT_LIMIT, str(DEFAULT_SPAN_LINK_COUNT_LIMIT) ) os.environ.setdefault( - OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, str(_DEFAULT_MAX_ATTR_LENGTH) + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, str(DEFAULT_MAX_ATTR_LENGTH) ) def _configure_headers(self) -> None: @@ -86,18 +89,34 @@ def _configure_headers(self) -> None: if access_token == "": return headers = "" - if "OTEL_EXPORTER_OTLP_HEADERS" in os.environ: - headers = os.environ["OTEL_EXPORTER_OTLP_HEADERS"] + "," + if OTEL_EXPORTER_OTLP_HEADERS in os.environ: + headers = os.environ[OTEL_EXPORTER_OTLP_HEADERS] + "," headers += "x-sf-token=" + access_token - os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = headers + os.environ[OTEL_EXPORTER_OTLP_HEADERS] = headers def _configure_resource_attributes(self) -> None: resource_attributes = "" - if "OTEL_RESOURCE_ATTRIBUTES" in os.environ: - resource_attributes = os.environ["OTEL_RESOURCE_ATTRIBUTES"] + "," - resource_attributes += _SPLUNK_DISTRO_VERSION_ATTR + "=" + __version__ - os.environ["OTEL_RESOURCE_ATTRIBUTES"] = resource_attributes + if OTEL_RESOURCE_ATTRIBUTES in os.environ: + resource_attributes = os.environ[OTEL_RESOURCE_ATTRIBUTES] + "," + resource_attributes += SPLUNK_DISTRO_VERSION_ATTR + "=" + __version__ + os.environ[OTEL_RESOURCE_ATTRIBUTES] = resource_attributes def _set_server_timing_propagator(self) -> None: - if _is_truthy_str(os.environ.get(_SPLUNK_TRACE_RESPONSE_HEADER_ENABLED, "true")): + if _is_truthy_str(os.environ.get("SPLUNK_TRACE_RESPONSE_HEADER_ENABLED", "true")): set_global_response_propagator(_ServerTimingResponsePropagator()) + + +class SplunkConfigurator(_OTelSDKConfigurator): + """ + The SDK configurator runs *after* the distro. + + This class is referenced in the local pyproject.toml: + + [tool.poetry.plugins."opentelemetry_configurator"] + configurator = "splunk_otel.distro:SplunkConfigurator" + """ + + def _configure(self, **kwargs): + super()._configure(**kwargs) + if _is_truthy(os.environ.get("SPLUNK_PROFILER_ENABLED", False)): + start_profiling() diff --git a/splunk_otel/environment_variables.py b/splunk_otel/environment_variables.py deleted file mode 100644 index 7ee6eae0..00000000 --- a/splunk_otel/environment_variables.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright Splunk Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -_SPLUNK_ACCESS_TOKEN = "SPLUNK_ACCESS_TOKEN" -_SPLUNK_TRACE_RESPONSE_HEADER_ENABLED = "SPLUNK_TRACE_RESPONSE_HEADER_ENABLED" diff --git a/splunk_otel/symbols.py b/splunk_otel/symbols.py deleted file mode 100644 index ce19d8bf..00000000 --- a/splunk_otel/symbols.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright Splunk Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -_DEFAULT_SERVICE_NAME = "unnamed-python-service" -_DEFAULT_EXPORTERS = "otlp" -_DEFAULT_MAX_ATTR_LENGTH = 12000 -_DEFAULT_SPAN_LINK_COUNT_LIMIT = 1000 - -_EXPORTER_OTLP = "otlp" -_EXPORTER_OTLP_GRPC = "otlp_proto_grpc" -_DEFAULT_OTEL_SERVICE_NAME = "unknown_service" - -_SPLUNK_DISTRO_VERSION_ATTR = "splunk.distro.version" -_SERVICE_NAME_ATTR = "service.name" -_TELEMETRY_VERSION_ATTR = "telemetry.auto.version" -_NO_SERVICE_NAME_WARNING = """service.name attribute is not set, your service is unnamed and will be difficult to identify. -set your service name using the OTEL_SERVICE_NAME environment variable. -E.g. `OTEL_SERVICE_NAME=""`""" - -_KNOWN_EXPORTER_PACKAGES = { - _EXPORTER_OTLP: "opentelemetry-exporter-otlp-proto-grpc", - _EXPORTER_OTLP_GRPC: "opentelemetry-exporter-otlp-proto-grpc", -} - -_LIMIT_UNSET_VALUE = "" diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/collector.yml b/tests/integration/collector.yml deleted file mode 100644 index 4622760a..00000000 --- a/tests/integration/collector.yml +++ /dev/null @@ -1,27 +0,0 @@ -extensions: - health_check: - -receivers: - otlp: - protocols: - grpc: - endpoint: 0.0.0.0:4317 - http: - endpoint: 0.0.0.0:4318 - - smartagent/signalfx-forwarder: - listenAddress: '0.0.0.0:9080' - type: signalfx-forwarder - -exporters: - logging: - httpsink: - endpoint: 0.0.0.0:8378 - - -service: - extensions: ["health_check"] - pipelines: - traces: - receivers: [otlp, smartagent/signalfx-forwarder] - exporters: [httpsink] diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py deleted file mode 100644 index e5916c14..00000000 --- a/tests/integration/conftest.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright Splunk Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from collections import namedtuple -from os import path - -import pytest -import requests - -IntegrationSession = namedtuple("Session", ("poll_url", "rootdir")) - - -@pytest.fixture(scope="session") -def docker_compose_file(pytestconfig): - return path.join( - str(pytestconfig.rootdir), "tests", "integration", "docker-compose.yml" - ) - - -@pytest.fixture(scope="session") -def integration(pytestconfig, docker_ip, docker_services): - port = docker_services.port_for("collector", 13133) - url = f"http://{docker_ip}:{port}" - docker_services.wait_until_responsive( - timeout=10, pause=0.1, check=lambda: is_responsive(url) - ) - return IntegrationSession( - poll_url=f"http://{docker_ip}:8378/spans", - rootdir=path.join(str(pytestconfig.rootdir), "tests", "integration"), - ) - - -@pytest.fixture(scope="session") -def integration_local(pytestconfig): - """Local non-docer based replacement for `integration` fixture""" - return IntegrationSession( - poll_url="http://localhost:8378/spans", - rootdir=path.join(str(pytestconfig.rootdir), "tests", "integration"), - ) - - -def is_responsive(url) -> bool: - try: - response = requests.get(url, timeout=10) - if response.status_code == 200: - return True - except requests.exceptions.ConnectionError: - pass - return False diff --git a/tests/integration/docker-compose.yml b/tests/integration/docker-compose.yml deleted file mode 100644 index 3290d1fc..00000000 --- a/tests/integration/docker-compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: '3' -services: - collector: - image: quay.io/signalfx/splunk-otel-collector:0.85.0 - environment: - - SPLUNK_CONFIG=/etc/otel/config.yml - volumes: - - ${PWD}/tests/integration/collector.yml:/etc/otel/config.yml - # for running docker compose up manually from the same dir - #- ${PWD}/collector.yml:/etc/otel/config.yml - ports: - - "4317:4317" - - "8378:8378" - - "9080:9080" - - "13133:13133" diff --git a/tests/integration/simple/main.py b/tests/integration/simple/main.py deleted file mode 100644 index 446f021f..00000000 --- a/tests/integration/simple/main.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright Splunk Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from opentelemetry import trace - -tracer = trace.get_tracer("simple", "0.1") - - -def main(): - with tracer.start_as_current_span( - "custom span", kind=trace.SpanKind.INTERNAL - ) as span: - span.add_event("event1", {"k1": "v1"}) - - -if __name__ == "__main__": - main() diff --git a/tests/integration/test_simple.py b/tests/integration/test_simple.py deleted file mode 100644 index 6d489c15..00000000 --- a/tests/integration/test_simple.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright Splunk Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import subprocess - -from requests_futures.sessions import FuturesSession - -from .conftest import IntegrationSession - - -def test_otlp_simple(integration: IntegrationSession): - session = FuturesSession() - # start polling collector for spans - future = session.get(integration.poll_url) - - # execute instrumented program - env = os.environ.copy() - subprocess.check_call( - ["splunk-py-trace", "python", f"{integration.rootdir}/simple/main.py"], - env=env, - ) - - # get result of poll and assert spans - response = future.result() - assert response.ok - spans = response.json() - assert len(spans) == 1 - span = spans[0] - - assert span["operationName"] == "custom span" - - tags = [ - {"key": "otel.library.name", "vStr": "simple"}, - {"key": "otel.library.version", "vStr": "0.1"}, - ] - for tag in tags: - assert tag in span["tags"] diff --git a/tests/unit/test_distro.py b/tests/unit/test_distro.py index cd1c7f36..dfed8bc8 100644 --- a/tests/unit/test_distro.py +++ b/tests/unit/test_distro.py @@ -13,16 +13,28 @@ # limitations under the License. import os +import pytest +from opentelemetry import trace +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter from opentelemetry.instrumentation.propagators import get_global_response_propagator from opentelemetry.sdk.trace import SpanLimits -from splunk_otel.distro import _SplunkDistro +from splunk_otel.distro import SplunkConfigurator, SplunkDistro from splunk_otel.propagators import _ServerTimingResponsePropagator -def test_distro_env(monkeypatch): - monkeypatch.setenv("SPLUNK_ACCESS_TOKEN", "s4cr4t") - sd = _SplunkDistro() +@pytest.fixture +def restore_env(): + original_env = os.environ.copy() + yield + os.environ.clear() + os.environ.update(original_env) + + +def test_distro_env(restore_env): + os.environ["SPLUNK_ACCESS_TOKEN"] = "s4cr4t" + + sd = SplunkDistro() sd.configure() assert os.getenv("OTEL_PYTHON_LOG_CORRELATION") == "true" assert os.getenv("OTEL_TRACES_EXPORTER") == "otlp" @@ -33,8 +45,13 @@ def test_distro_env(monkeypatch): assert isinstance(get_global_response_propagator(), _ServerTimingResponsePropagator) -def test_default_limits(): - _SplunkDistro().configure() +def test_default_limits(restore_env): + # we're not testing SplunkDistro here but configure() sets environment variables read by SpanLimits + distro = SplunkDistro() + distro.configure() + + # SpanLimits() is instantiated by the TracerProvider constructor + # for testing, we instantiate it directly limits = SpanLimits() assert limits.max_events is None assert limits.max_span_attributes is None @@ -44,3 +61,18 @@ def test_default_limits(): assert limits.max_links == 1000 assert limits.max_attribute_length == 12000 assert limits.max_span_attribute_length == 12000 + + +def test_configurator(): + # we're not testing SplunkDistro here but configure() sets environment variables read by SplunkConfigurator + distro = SplunkDistro() + distro.configure() + + sp = SplunkConfigurator() + sp.configure() + tp = trace.get_tracer_provider() + sync_multi_span_proc = getattr(tp, "_active_span_processor") + assert len(sync_multi_span_proc._span_processors) == 1 + batch_span_processor = sync_multi_span_proc._span_processors[0] + otlp_span_exporter = batch_span_processor.span_exporter + assert isinstance(otlp_span_exporter, OTLPSpanExporter) From aec41f311d536c737b9d83cd1aee4e6bc00e73fa Mon Sep 17 00:00:00 2001 From: Pablo Collins Date: Fri, 26 Jul 2024 18:02:45 -0400 Subject: [PATCH 30/30] Add two oteltest scripts, http and grpc --- pyproject.toml | 2 +- tests/oteltest/lib.py | 11 +++++++++++ tests/oteltest/trace_loop_grpc.py | 28 ++++++++++++++++++++++++++++ tests/oteltest/trace_loop_http.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 tests/oteltest/lib.py create mode 100644 tests/oteltest/trace_loop_grpc.py create mode 100644 tests/oteltest/trace_loop_http.py diff --git a/pyproject.toml b/pyproject.toml index a0413702..91605b8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ splk-py-trace = 'splunk_otel.cmd.trace:run_deprecated' splk-py-trace-bootstrap = 'splunk_otel.cmd.bootstrap:run_deprecated' [tool.poetry.plugins."opentelemetry_distro"] -splunk_distro = "splunk_otel.distro:_SplunkDistro" +splunk_distro = "splunk_otel.distro:SplunkDistro" [tool.poetry.plugins."opentelemetry_configurator"] configurator = "splunk_otel.distro:SplunkConfigurator" diff --git a/tests/oteltest/lib.py b/tests/oteltest/lib.py new file mode 100644 index 00000000..4162cbbd --- /dev/null +++ b/tests/oteltest/lib.py @@ -0,0 +1,11 @@ +import time + +from opentelemetry import trace + + +def trace_loop(): + tracer = trace.get_tracer('oteltest') + for i in range(16): + with tracer.start_as_current_span(f"loop-{i}"): + print(i) + time.sleep(1) diff --git a/tests/oteltest/trace_loop_grpc.py b/tests/oteltest/trace_loop_grpc.py new file mode 100644 index 00000000..d626981e --- /dev/null +++ b/tests/oteltest/trace_loop_grpc.py @@ -0,0 +1,28 @@ +from tests.oteltest.lib import trace_loop + +if __name__ == '__main__': + trace_loop() + + +class MyOtelTest: + + def environment_variables(self): + return {} + + def requirements(self): + return ( + "dist/splunk_opentelemetry-2.0-py3-none-any.whl", + "opentelemetry-exporter-otlp-proto-grpc==1.24.0", + ) + + def wrapper_command(self): + return "splunk-py-trace" + + def on_start(self): + print("on_start") + + def on_stop(self, tel, stdout: str, stderr: str, returncode: int) -> None: + import oteltest.telemetry + + print("on_stop") + assert len(oteltest.telemetry.stack_traces(tel)) == 16 diff --git a/tests/oteltest/trace_loop_http.py b/tests/oteltest/trace_loop_http.py new file mode 100644 index 00000000..51e8f31b --- /dev/null +++ b/tests/oteltest/trace_loop_http.py @@ -0,0 +1,30 @@ +from tests.oteltest.lib import trace_loop + +if __name__ == '__main__': + trace_loop() + + +class MyOtelTest: + + def environment_variables(self): + return { + "OTEL_SERVICE_NAME": "my-service", + "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf", + } + + def requirements(self): + return "dist/splunk_opentelemetry-2.0-py3-none-any.whl", "opentelemetry-exporter-otlp-proto-http==1.24.0" + + def wrapper_command(self): + return "splunk-py-trace" + + def on_start(self): + print("on_start") + + def on_stop(self, tel, stdout: str, stderr: str, returncode: int) -> None: + import oteltest.telemetry + + assert len(oteltest.telemetry.stack_traces(tel)) == 16 + + def is_http(self): + return True