diff --git a/.flake8 b/.flake8 index f511c4c3e0..39288e9b1b 100644 --- a/.flake8 +++ b/.flake8 @@ -20,3 +20,4 @@ exclude = ext/opentelemetry-ext-jaeger/build/* docs/examples/opentelemetry-example-app/src/opentelemetry_example_app/grpc/gen/ docs/examples/opentelemetry-example-app/build/* + opentelemetry-proto/src/opentelemetry/proto/ diff --git a/.isort.cfg b/.isort.cfg index ae2dfc3252..fd2ecc7865 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -13,6 +13,6 @@ line_length=79 ; docs: https://github.com/timothycrosley/isort#multi-line-output-modes multi_line_output=3 skip=target -skip_glob=**/gen/*,.venv*/*,venv*/* +skip_glob=**/gen/*,.venv*/*,venv*/*,**/proto/* known_first_party=opentelemetry,opentelemetry_example_app known_third_party=psutil,pytest,redis,redis_opentracing diff --git a/.pylintrc b/.pylintrc index 1aa1e10d0b..01c96ea395 100644 --- a/.pylintrc +++ b/.pylintrc @@ -7,7 +7,7 @@ extension-pkg-whitelist= # Add files or directories to the blacklist. They should be base names, not # paths. -ignore=CVS,gen +ignore=CVS,gen,proto # Add files or directories matching the regex patterns to the blacklist. The # regex matches against base names, not paths. diff --git a/README.md b/README.md index 613b4e6acb..6f1be8fbce 100644 --- a/README.md +++ b/README.md @@ -103,11 +103,11 @@ Meeting notes are available as a public [Google doc](https://docs.google.com/doc Approvers ([@open-telemetry/python-approvers](https://github.com/orgs/open-telemetry/teams/python-approvers)): -- [Carlos Alberto Cortez](https://github.com/carlosalberto), LightStep +- [Carlos Alberto Cortez](https://github.com/carlosalberto), Lightstep +- [Tahir H. Butt](https://github.com/majorgreys) DataDog - [Chris Kleinknecht](https://github.com/c24t), Google - [Diego Hurtado](https://github.com/ocelotl) - [Hector Hernandez](https://github.com/hectorhdzg), Microsoft -- [Leighton Chen](https://github.com/lzchen), Microsoft - [Mauricio Vásquez](https://github.com/mauriciovasquezbernal), Kinvolk - [Reiley Yang](https://github.com/reyang), Microsoft @@ -115,17 +115,18 @@ Approvers ([@open-telemetry/python-approvers](https://github.com/orgs/open-telem Maintainers ([@open-telemetry/python-maintainers](https://github.com/orgs/open-telemetry/teams/python-maintainers)): -- [Alex Boten](https://github.com/codeboten), LightStep +- [Alex Boten](https://github.com/codeboten), Lightstep +- [Leighton Chen](https://github.com/lzchen), Microsoft - [Yusuke Tsutsumi](https://github.com/toumorokoshi), Zillow Group +*Find more about the maintainer role in [community repository](https://github.com/open-telemetry/community/blob/master/community-membership.md#maintainer).* + ### Thanks to all the people who already contributed! -*Find more about the maintainer role in [community repository](https://github.com/open-telemetry/community/blob/master/community-membership.md#maintainer).* - ## Release Schedule OpenTelemetry Python is under active development. diff --git a/dev-requirements.txt b/dev-requirements.txt index be74d804b3..8ea405d92a 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -2,12 +2,10 @@ pylint==2.4.4 flake8==3.7.9 isort~=4.3 black>=19.3b0,==19.* -mypy==0.740 +mypy==0.770 sphinx~=2.1 sphinx-rtd-theme~=0.4 sphinx-autodoc-typehints~=1.10.2 pytest!=5.2.3 pytest-cov>=2.8 -readme-renderer~=24.0 -httpretty~=1.0 -opentracing~=2.2.0 \ No newline at end of file +readme-renderer~=24.0 \ No newline at end of file diff --git a/docs-requirements.txt b/docs-requirements.txt index db10f6f9ee..10ccf1b21c 100644 --- a/docs-requirements.txt +++ b/docs-requirements.txt @@ -14,7 +14,9 @@ mysql-connector-python~=8.0 opentracing~=2.2.0 prometheus_client>=0.5.0,<1.0.0 psycopg2-binary>=2.7.3.1 +pymemcache~=1.3 pymongo~=3.1 +pyramid>=1.7 redis>=2.6 sqlalchemy>=1.0 thrift>=0.10.0 @@ -22,3 +24,6 @@ wrapt>=1.0.0,<2.0.0 psutil~=5.7.0 boto~=2.0 google-cloud-trace >=0.23.0 +google-cloud-monitoring>=0.36.0 +botocore~=1.0 +starlette~=0.13 diff --git a/docs/api/trace.rst b/docs/api/trace.rst index 00823aa036..411e31023e 100644 --- a/docs/api/trace.rst +++ b/docs/api/trace.rst @@ -8,8 +8,9 @@ Submodules trace.sampling trace.status + trace.span Module contents --------------- -.. automodule:: opentelemetry.trace +.. automodule:: opentelemetry.trace \ No newline at end of file diff --git a/docs/api/trace.span.rst b/docs/api/trace.span.rst new file mode 100644 index 0000000000..94b36930df --- /dev/null +++ b/docs/api/trace.span.rst @@ -0,0 +1,7 @@ +opentelemetry.trace.span +======================== + +.. automodule:: opentelemetry.trace.span + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/auto_instrumentation/auto_instrumentation.rst b/docs/auto_instrumentation/auto_instrumentation.rst deleted file mode 100644 index c1fd0eff2a..0000000000 --- a/docs/auto_instrumentation/auto_instrumentation.rst +++ /dev/null @@ -1,15 +0,0 @@ -OpenTelemetry Python Autoinstrumentation -======================================== - -.. automodule:: opentelemetry.auto_instrumentation - :members: - :undoc-members: - :show-inheritance: - -Submodules ----------- - -.. toctree:: - :maxdepth: 1 - - instrumentor diff --git a/docs/auto_instrumentation/instrumentor.rst b/docs/auto_instrumentation/instrumentor.rst deleted file mode 100644 index c94c0237f5..0000000000 --- a/docs/auto_instrumentation/instrumentor.rst +++ /dev/null @@ -1,7 +0,0 @@ -opentelemetry.auto_instrumentation.instrumentor package -======================================================= - -.. automodule:: opentelemetry.auto_instrumentation.instrumentor - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py index 42fcf29f4f..74ae754c60 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,7 +27,7 @@ source_dirs = [ os.path.abspath("../opentelemetry-api/src/"), os.path.abspath("../opentelemetry-sdk/src/"), - os.path.abspath("../opentelemetry-auto-instrumentation/src/"), + os.path.abspath("../opentelemetry-instrumentation/src/"), ] ext = "../ext" diff --git a/docs/examples/auto-instrumentation/README.rst b/docs/examples/auto-instrumentation/README.rst index 6592ecb958..b5a7649503 100644 --- a/docs/examples/auto-instrumentation/README.rst +++ b/docs/examples/auto-instrumentation/README.rst @@ -72,7 +72,7 @@ Installation .. code:: sh $ pip install opentelemetry-sdk - $ pip install opentelemetry-auto-instrumentation + $ pip install opentelemetry-instrumentation $ pip install opentelemetry-ext-flask $ pip install requests @@ -138,7 +138,7 @@ and run this instead: .. code:: sh - $ opentelemetry-auto-instrumentation python server_uninstrumented.py + $ opentelemetry-instrument python server_uninstrumented.py In the console where you previously executed ``client.py``, run again this again: diff --git a/docs/examples/basic_meter/basic_metrics.py b/docs/examples/basic_meter/basic_metrics.py index b9ff8d8741..e65aa788b8 100644 --- a/docs/examples/basic_meter/basic_metrics.py +++ b/docs/examples/basic_meter/basic_metrics.py @@ -26,9 +26,6 @@ from opentelemetry import metrics from opentelemetry.sdk.metrics import Counter, MeterProvider, ValueRecorder from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter -from opentelemetry.sdk.metrics.export.controller import PushController - -stateful = True print( "Starting example, values will be printed to the console every 5 seconds." @@ -37,7 +34,11 @@ # Stateful determines whether how metrics are collected: if true, metrics # accumulate over the process lifetime. If false, metrics are reset at the # beginning of each collection interval. -metrics.set_meter_provider(MeterProvider(stateful)) +stateful = True + +# Sets the global MeterProvider instance +metrics.set_meter_provider(MeterProvider()) + # The Meter is responsible for creating and recording metrics. Each meter has a # unique name, which we set as the module's name here. meter = metrics.get_meter(__name__) @@ -45,9 +46,9 @@ # Exporter to export metrics to the console exporter = ConsoleMetricsExporter() -# A PushController collects metrics created from meter and exports it via the -# exporter every interval -controller = PushController(meter=meter, exporter=exporter, interval=5) +# start_pipeline will notify the MeterProvider to begin collecting/exporting +# metrics with the given meter, exporter and interval in seconds +metrics.get_meter_provider().start_pipeline(meter, exporter, 5) # Metric instruments allow to capture measurements requests_counter = meter.create_metric( @@ -77,7 +78,7 @@ # Update the metric instruments using the direct calling convention requests_counter.add(25, staging_labels) requests_size.record(100, staging_labels) -time.sleep(5) +time.sleep(10) requests_counter.add(50, staging_labels) requests_size.record(5000, staging_labels) diff --git a/docs/examples/basic_meter/calling_conventions.py b/docs/examples/basic_meter/calling_conventions.py index f8cc3dddbb..3615f60d7d 100644 --- a/docs/examples/basic_meter/calling_conventions.py +++ b/docs/examples/basic_meter/calling_conventions.py @@ -21,13 +21,11 @@ from opentelemetry import metrics from opentelemetry.sdk.metrics import Counter, MeterProvider, ValueRecorder from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter -from opentelemetry.sdk.metrics.export.controller import PushController # Use the meter type provided by the SDK package metrics.set_meter_provider(MeterProvider()) meter = metrics.get_meter(__name__) -exporter = ConsoleMetricsExporter() -controller = PushController(meter=meter, exporter=exporter, interval=5) +metrics.get_meter_provider().start_pipeline(meter, ConsoleMetricsExporter(), 5) requests_counter = meter.create_metric( name="requests", @@ -62,7 +60,7 @@ # You can record metrics directly using the metric instrument. You pass in # labels that you would like to record for. requests_counter.add(25, labels) -time.sleep(5) +time.sleep(10) print("Updating using a bound instrument...") # You can record metrics with bound metric instruments. Bound metric diff --git a/docs/examples/basic_meter/observer.py b/docs/examples/basic_meter/observer.py index aa70abe2a4..076c416c0a 100644 --- a/docs/examples/basic_meter/observer.py +++ b/docs/examples/basic_meter/observer.py @@ -19,15 +19,12 @@ import psutil from opentelemetry import metrics -from opentelemetry.sdk.metrics import MeterProvider +from opentelemetry.sdk.metrics import MeterProvider, ValueObserver from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter -from opentelemetry.sdk.metrics.export.batcher import UngroupedBatcher -from opentelemetry.sdk.metrics.export.controller import PushController metrics.set_meter_provider(MeterProvider()) meter = metrics.get_meter(__name__) -exporter = ConsoleMetricsExporter() -controller = PushController(meter=meter, exporter=exporter, interval=2) +metrics.get_meter_provider().start_pipeline(meter, ConsoleMetricsExporter(), 5) # Callback to gather cpu usage @@ -43,6 +40,7 @@ def get_cpu_usage_callback(observer): description="per-cpu usage", unit="1", value_type=float, + observer_type=ValueObserver, label_keys=("cpu_number",), ) @@ -59,6 +57,7 @@ def get_ram_usage_callback(observer): description="RAM memory usage", unit="1", value_type=float, + observer_type=ValueObserver, label_keys=(), ) diff --git a/docs/examples/cloud_monitoring/README.rst b/docs/examples/cloud_monitoring/README.rst new file mode 100644 index 0000000000..5ad5421794 --- /dev/null +++ b/docs/examples/cloud_monitoring/README.rst @@ -0,0 +1,35 @@ +Cloud Monitoring Exporter Example +================================= + +These examples show how to use OpenTelemetry to send metrics data to Cloud Monitoring. + + +Basic Example +------------- + +To use this exporter you first need to: + * `Create a Google Cloud project `_. + * Enable the Cloud Monitoring API (aka Stackdriver Monitoring API) in the project `here `_. + * Enable `Default Application Credentials `_. + +* Installation + +.. code-block:: sh + + pip install opentelemetry-api + pip install opentelemetry-sdk + pip install opentelemetry-exporter-cloud-monitoring + +* Run example + +.. code-block:: sh + + python basic_metrics.py + +Viewing Output +-------------------------- + +After running the example: + * Go to the `Cloud Monitoring Metrics Explorer page `_. + * In "Find resource type and metric" enter "OpenTelemetry/request_counter". + * You can filter by labels and change the graphical output here as well. diff --git a/docs/examples/cloud_monitoring/basic_metrics.py b/docs/examples/cloud_monitoring/basic_metrics.py new file mode 100644 index 0000000000..fa00fc068b --- /dev/null +++ b/docs/examples/cloud_monitoring/basic_metrics.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# Copyright The OpenTelemetry Authors +# +# 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 time + +from opentelemetry import metrics +from opentelemetry.exporter.cloud_monitoring import ( + CloudMonitoringMetricsExporter, +) +from opentelemetry.sdk.metrics import Counter, MeterProvider + +metrics.set_meter_provider(MeterProvider()) +meter = metrics.get_meter(__name__) +metrics.get_meter_provider().start_pipeline( + meter, CloudMonitoringMetricsExporter(), 5 +) + +requests_counter = meter.create_metric( + name="request_counter", + description="number of requests", + unit="1", + value_type=int, + metric_type=Counter, + label_keys=("environment"), +) + +staging_labels = {"environment": "staging"} + +for i in range(20): + requests_counter.add(25, staging_labels) + time.sleep(10) diff --git a/docs/examples/cloud_trace_exporter/README.rst b/docs/examples/cloud_trace_exporter/README.rst index 871422356a..7c6c2c4c40 100644 --- a/docs/examples/cloud_trace_exporter/README.rst +++ b/docs/examples/cloud_trace_exporter/README.rst @@ -9,7 +9,7 @@ Basic Example To use this exporter you first need to: * A Google Cloud project. You can `create one here. `_ - * Enable Cloud Trace API (aka StackDriver Trace API) in the project `here. `_ + * Enable Cloud Trace API (aka Stackdriver Trace API) in the project `here. `_ * Enable `Default Application Credentials. `_ * Installation @@ -20,10 +20,11 @@ To use this exporter you first need to: pip install opentelemetry-sdk pip install opentelemetry-exporter-cloud-trace -* Run example +* Run example locally .. code-block:: sh + cd opentelemetry-python/docs/examples/cloud_trace_exporter python basic_trace.py Checking Output @@ -31,4 +32,19 @@ Checking Output After running any of these examples, you can go to `Cloud Trace overview `_ to see the results. -* `More information about exporters in general `_ \ No newline at end of file + +Further Reading +-------------------------- + +* `More information about exporters in general `_ + +Troubleshooting +-------------------------- + +Running basic_trace.py hangs: +############################# + * Make sure you've setup Application Default Credentials. Either run ``gcloud auth application-default login`` or set the ``GOOGLE_APPLICATION_CREDENTIALS`` environment variable to be a path to a service account token file. + +Getting error ``google.api_core.exceptions.ResourceExhausted: 429 Resource has been exhausted``: +################################################################################################ + * Check that you've enabled the `Cloud Trace (Stackdriver Trace) API `_ \ No newline at end of file diff --git a/docs/examples/datadog_exporter/README.rst b/docs/examples/datadog_exporter/README.rst index 961ac9ca05..d851550b27 100644 --- a/docs/examples/datadog_exporter/README.rst +++ b/docs/examples/datadog_exporter/README.rst @@ -32,10 +32,15 @@ Basic Example .. code-block:: sh - python datadog_exporter.py + python basic_example.py -Auto-Instrumention Example --------------------------- + +.. code-block:: sh + + python basic_example.py + +Distributed Example +------------------- * Installation @@ -44,7 +49,7 @@ Auto-Instrumention Example pip install opentelemetry-api pip install opentelemetry-sdk pip install opentelemetry-ext-datadog - pip install opentelemetry-auto-instrumentation + pip install opentelemetry-instrumentation pip install opentelemetry-ext-flask pip install flask pip install requests @@ -66,16 +71,25 @@ Auto-Instrumention Example .. code-block:: sh - opentelemetry-auto-instrumentation python server.py + opentelemetry-instrument python server.py * Run client .. code-block:: sh - opentelemetry-auto-instrumentation python client.py testing + opentelemetry-instrument python client.py testing * Run client with parameter to raise error .. code-block:: sh - opentelemetry-auto-instrumentation python client.py error + opentelemetry-instrument python client.py error + +* Run Datadog instrumented client + +The OpenTelemetry instrumented server is set up with propagation of Datadog trace context. + +.. code-block:: sh + + pip install ddtrace + ddtrace-run python datadog_client.py testing diff --git a/docs/examples/datadog_exporter/datadog_exporter.py b/docs/examples/datadog_exporter/basic_example.py similarity index 99% rename from docs/examples/datadog_exporter/datadog_exporter.py rename to docs/examples/datadog_exporter/basic_example.py index 0b3af99223..a41f9e0462 100644 --- a/docs/examples/datadog_exporter/datadog_exporter.py +++ b/docs/examples/datadog_exporter/basic_example.py @@ -31,6 +31,7 @@ span_processor = DatadogExportSpanProcessor(exporter) trace.get_tracer_provider().add_span_processor(span_processor) + with tracer.start_as_current_span("foo"): with tracer.start_as_current_span("bar"): with tracer.start_as_current_span("baz"): diff --git a/docs/examples/datadog_exporter/datadog_client.py b/docs/examples/datadog_exporter/datadog_client.py new file mode 100644 index 0000000000..26c463c3f5 --- /dev/null +++ b/docs/examples/datadog_exporter/datadog_client.py @@ -0,0 +1,23 @@ +# Copyright The OpenTelemetry Authors +# +# 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 sys import argv + +import requests + +requested = requests.get( + "http://localhost:8082/server_request", params={"param": argv[1]} +) +assert requested.status_code == 200 +print(requested.text) diff --git a/docs/examples/datadog_exporter/server.py b/docs/examples/datadog_exporter/server.py index 0d545e2b7b..e2099fdf25 100644 --- a/docs/examples/datadog_exporter/server.py +++ b/docs/examples/datadog_exporter/server.py @@ -14,11 +14,12 @@ from flask import Flask, request -from opentelemetry import trace +from opentelemetry import propagators, trace from opentelemetry.ext.datadog import ( DatadogExportSpanProcessor, DatadogSpanExporter, ) +from opentelemetry.ext.datadog.propagator import DatadogFormat from opentelemetry.sdk.trace import TracerProvider app = Flask(__name__) @@ -33,6 +34,21 @@ ) ) +# append Datadog format for propagation to and from Datadog instrumented services +global_httptextformat = propagators.get_global_httptextformat() +if isinstance( + global_httptextformat, propagators.composite.CompositeHTTPPropagator +) and not any( + isinstance(p, DatadogFormat) for p in global_httptextformat._propagators +): + propagators.set_global_httptextformat( + propagators.composite.CompositeHTTPPropagator( + global_httptextformat._propagators + [DatadogFormat()] + ) + ) +else: + propagators.set_global_httptextformat(DatadogFormat()) + tracer = trace.get_tracer(__name__) diff --git a/docs/examples/opencensus-exporter-metrics/collector.py b/docs/examples/opencensus-exporter-metrics/collector.py index 89dabd12ea..725f07b77a 100644 --- a/docs/examples/opencensus-exporter-metrics/collector.py +++ b/docs/examples/opencensus-exporter-metrics/collector.py @@ -21,7 +21,6 @@ OpenCensusMetricsExporter, ) from opentelemetry.sdk.metrics import Counter, MeterProvider -from opentelemetry.sdk.metrics.export.controller import PushController exporter = OpenCensusMetricsExporter( service_name="basic-service", endpoint="localhost:55678" @@ -29,7 +28,7 @@ metrics.set_meter_provider(MeterProvider()) meter = metrics.get_meter(__name__) -controller = PushController(meter, exporter, 5) +metrics.get_meter_provider().start_pipeline(meter, exporter, 5) requests_counter = meter.create_metric( name="requests", diff --git a/docs/examples/opentelemetry-example-app/setup.cfg b/docs/examples/opentelemetry-example-app/setup.cfg new file mode 100644 index 0000000000..88aa507518 --- /dev/null +++ b/docs/examples/opentelemetry-example-app/setup.cfg @@ -0,0 +1,62 @@ +# Copyright The OpenTelemetry Authors +# +# 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. +# +[metadata] +name = opentelemetry-example-app +description = OpenTelemetry Example App +long_description = file: README.rst +long_description_content_type = text/x-rst +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +url = https://github.com/open-telemetry/opentelemetry-python/tree/master/opentelemetry-example-app +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + +[options] +python_requires = >=3.4 +package_dir= + =src +packages = find_namespace: +zip_safe = False +include_package_data = True +install_requires = + typing; python_version<'3.5' + opentelemetry-api == 0.10.dev0 + opentelemetry-sdk == 0.10.dev0 + opentelemetry-ext-requests == 0.10.dev0 + opentelemetry-ext-flask == 0.10.dev0 + flask + requests + protobuf~=3.11 + +[options.packages.find] +where = src +include = opentelemetry_example_app + +[options.entry_points] +opentelemetry_meter_provider = + sdk_meter_provider = opentelemetry.sdk.metrics:MeterProvider +opentelemetry_tracer_provider = + sdk_tracer_provider = opentelemetry.sdk.trace:TracerProvider diff --git a/docs/examples/opentelemetry-example-app/setup.py b/docs/examples/opentelemetry-example-app/setup.py index 4d87c27afc..07b731b24e 100644 --- a/docs/examples/opentelemetry-example-app/setup.py +++ b/docs/examples/opentelemetry-example-app/setup.py @@ -11,44 +11,16 @@ # 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 setuptools -setuptools.setup( - name="opentelemetry-example-app", - version="0.9.dev0", - author="OpenTelemetry Authors", - author_email="cncf-opentelemetry-contributors@lists.cncf.io", - classifiers=[ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - ], - description="OpenTelemetry Python API", - include_package_data=True, - long_description=open("README.rst").read(), - install_requires=[ - "typing; python_version<'3.5'", - "opentelemetry-api", - "opentelemetry-sdk", - "opentelemetry-ext-requests", - "opentelemetry-ext-flask", - "flask", - "requests", - "protobuf~=3.11", - ], - license="Apache-2.0", - package_dir={"": "src"}, - packages=setuptools.find_namespace_packages(where="src"), - url=( - "https://github.com/open-telemetry/opentelemetry-python" - "/tree/master/opentelemetry-example-app" - ), - zip_safe=False, +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join( + BASE_DIR, "src", "opentelemetry_example_app", "version.py" ) +PACKAGE_INFO = {} +with open(VERSION_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup(version=PACKAGE_INFO["__version__"],) diff --git a/opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/version.py b/docs/examples/opentelemetry-example-app/src/opentelemetry_example_app/version.py similarity index 95% rename from opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/version.py rename to docs/examples/opentelemetry-example-app/src/opentelemetry_example_app/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/version.py +++ b/docs/examples/opentelemetry-example-app/src/opentelemetry_example_app/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/docs/ext/botocore/botocore.rst b/docs/ext/botocore/botocore.rst new file mode 100644 index 0000000000..43f702a904 --- /dev/null +++ b/docs/ext/botocore/botocore.rst @@ -0,0 +1,7 @@ +OpenTelemetry Botocore Integration +================================== + +.. automodule:: opentelemetry.ext.botocore + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/ext/cloud_monitoring/cloud_monitoring.rst b/docs/ext/cloud_monitoring/cloud_monitoring.rst new file mode 100644 index 0000000000..a3a4f5660a --- /dev/null +++ b/docs/ext/cloud_monitoring/cloud_monitoring.rst @@ -0,0 +1,7 @@ +OpenTelemetry Cloud Monitoring Exporter +======================================= + +.. automodule:: opentelemetry.exporter.cloud_monitoring + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/docs/ext/otlp/otlp.rst b/docs/ext/otlp/otlp.rst new file mode 100644 index 0000000000..4739d21a58 --- /dev/null +++ b/docs/ext/otlp/otlp.rst @@ -0,0 +1,7 @@ +Opentelemetry OTLP Exporter +=========================== + +.. automodule:: opentelemetry.ext.otlp + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/ext/pymemcache/pymemcache.rst b/docs/ext/pymemcache/pymemcache.rst new file mode 100644 index 0000000000..c64e00cb59 --- /dev/null +++ b/docs/ext/pymemcache/pymemcache.rst @@ -0,0 +1,7 @@ +OpenTelemetry pymemcache Integration +==================================== + +.. automodule:: opentelemetry.ext.pymemcache + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/ext/pyramid/pyramid.rst b/docs/ext/pyramid/pyramid.rst new file mode 100644 index 0000000000..b46718c387 --- /dev/null +++ b/docs/ext/pyramid/pyramid.rst @@ -0,0 +1,7 @@ +OpenTelemetry Pyramid Integration +================================= + +.. automodule:: opentelemetry.ext.pyramid + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/ext/sqlite3/sqlite3.rst b/docs/ext/sqlite3/sqlite3.rst new file mode 100644 index 0000000000..9537ff58bf --- /dev/null +++ b/docs/ext/sqlite3/sqlite3.rst @@ -0,0 +1,7 @@ +OpenTelemetry SQLite3 Integration +================================= + +.. automodule:: opentelemetry.ext.sqlite3 + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/ext/starlette/starlette.rst b/docs/ext/starlette/starlette.rst new file mode 100644 index 0000000000..8e2d1d7bc8 --- /dev/null +++ b/docs/ext/starlette/starlette.rst @@ -0,0 +1,9 @@ +.. include:: ../../../ext/opentelemetry-instrumentation-starlette/README.rst + +API +--- + +.. automodule:: opentelemetry.instrumentation.starlette + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 2d26e24f83..b25efa8acc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -68,7 +68,7 @@ install api/api sdk/sdk - auto_instrumentation/auto_instrumentation + instrumentation/instrumentation .. toctree:: :maxdepth: 2 diff --git a/docs/instrumentation/instrumentation.rst b/docs/instrumentation/instrumentation.rst new file mode 100644 index 0000000000..bcb85043f4 --- /dev/null +++ b/docs/instrumentation/instrumentation.rst @@ -0,0 +1,15 @@ +OpenTelemetry Python Instrumentation +==================================== + +.. automodule:: opentelemetry.instrumentation + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + :maxdepth: 1 + + instrumentor diff --git a/docs/instrumentation/instrumentor.rst b/docs/instrumentation/instrumentor.rst new file mode 100644 index 0000000000..5c0c010ff6 --- /dev/null +++ b/docs/instrumentation/instrumentor.rst @@ -0,0 +1,7 @@ +opentelemetry.instrumentation.instrumentor package +================================================== + +.. automodule:: opentelemetry.instrumentation.instrumentor + :members: + :undoc-members: + :show-inheritance: diff --git a/eachdist.ini b/eachdist.ini index b573b838fd..a2822d97b4 100644 --- a/eachdist.ini +++ b/eachdist.ini @@ -4,7 +4,9 @@ sortfirst= opentelemetry-api opentelemetry-sdk - opentelemetry-auto-instrumentation + opentelemetry-instrumentation + opentelemetry-proto + tests/util ext/opentelemetry-ext-wsgi ext/opentelemetry-ext-dbapi ext/* diff --git a/ext/opentelemetry-exporter-cloud-monitoring/README.rst b/ext/opentelemetry-exporter-cloud-monitoring/README.rst new file mode 100644 index 0000000000..f1fd52528c --- /dev/null +++ b/ext/opentelemetry-exporter-cloud-monitoring/README.rst @@ -0,0 +1,18 @@ +OpenTelemetry Cloud Monitoring Exporters +======================================== + +This library provides classes for exporting metrics data to Google Cloud Monitoring. + +Installation +------------ + +:: + + pip install opentelemetry-exporter-cloud-monitoring + +References +---------- + +* `OpenTelemetry Cloud Monitoring Exporter `_ +* `Cloud Monitoring `_ +* `OpenTelemetry Project `_ \ No newline at end of file diff --git a/ext/opentelemetry-exporter-cloud-monitoring/setup.cfg b/ext/opentelemetry-exporter-cloud-monitoring/setup.cfg new file mode 100644 index 0000000000..8875937751 --- /dev/null +++ b/ext/opentelemetry-exporter-cloud-monitoring/setup.cfg @@ -0,0 +1,51 @@ + +# Copyright OpenTelemetry Authors +# +# 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. +# +[metadata] +name = opentelemetry-exporter-cloud-monitoring +description = Cloud Monitoring integration for OpenTelemetry +long_description = file: README.rst +long_description_content_type = text/x-rst +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +url = https://github.com/open-telemetry/opentelemetry-python/ext/opentelemetry-exporter-cloud-monitoring +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + +[options] +python_requires = >=3.4 +package_dir= + =src +packages=find_namespace: +install_requires = + opentelemetry-api + opentelemetry-sdk + google-cloud-monitoring + +[options.packages.find] +where = src + +[options.extras_require] +test = diff --git a/ext/opentelemetry-exporter-cloud-monitoring/setup.py b/ext/opentelemetry-exporter-cloud-monitoring/setup.py new file mode 100644 index 0000000000..0ca88bc330 --- /dev/null +++ b/ext/opentelemetry-exporter-cloud-monitoring/setup.py @@ -0,0 +1,31 @@ +# Copyright OpenTelemetry Authors +# +# 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 setuptools + +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join( + BASE_DIR, + "src", + "opentelemetry", + "exporter", + "cloud_monitoring", + "version.py", +) +PACKAGE_INFO = {} +with open(VERSION_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup(version=PACKAGE_INFO["__version__"]) diff --git a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py new file mode 100644 index 0000000000..072301a274 --- /dev/null +++ b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py @@ -0,0 +1,171 @@ +import logging +from typing import Optional, Sequence + +import google.auth +from google.api.label_pb2 import LabelDescriptor +from google.api.metric_pb2 import MetricDescriptor +from google.cloud.monitoring_v3 import MetricServiceClient +from google.cloud.monitoring_v3.proto.metric_pb2 import TimeSeries + +from opentelemetry.sdk.metrics.export import ( + MetricRecord, + MetricsExporter, + MetricsExportResult, +) +from opentelemetry.sdk.metrics.export.aggregate import SumAggregator + +logger = logging.getLogger(__name__) +MAX_BATCH_WRITE = 200 +WRITE_INTERVAL = 10 + + +# pylint is unable to resolve members of protobuf objects +# pylint: disable=no-member +class CloudMonitoringMetricsExporter(MetricsExporter): + """ Implementation of Metrics Exporter to Google Cloud Monitoring""" + + def __init__(self, project_id=None, client=None): + self.client = client or MetricServiceClient() + if not project_id: + _, self.project_id = google.auth.default() + else: + self.project_id = project_id + self.project_name = self.client.project_path(self.project_id) + self._metric_descriptors = {} + self._last_updated = {} + + def _add_resource_info(self, series: TimeSeries) -> None: + """Add Google resource specific information (e.g. instance id, region). + + Args: + series: ProtoBuf TimeSeries + """ + # TODO: Leverage this better + + def _batch_write(self, series: TimeSeries) -> None: + """ Cloud Monitoring allows writing up to 200 time series at once + + :param series: ProtoBuf TimeSeries + :return: + """ + write_ind = 0 + while write_ind < len(series): + self.client.create_time_series( + self.project_name, + series[write_ind : write_ind + MAX_BATCH_WRITE], + ) + write_ind += MAX_BATCH_WRITE + + def _get_metric_descriptor( + self, record: MetricRecord + ) -> Optional[MetricDescriptor]: + """ We can map Metric to MetricDescriptor using Metric.name or + MetricDescriptor.type. We create the MetricDescriptor if it doesn't + exist already and cache it. Note that recreating MetricDescriptors is + a no-op if it already exists. + + :param record: + :return: + """ + instrument = record.instrument + descriptor_type = "custom.googleapis.com/OpenTelemetry/{}".format( + instrument.name + ) + if descriptor_type in self._metric_descriptors: + return self._metric_descriptors[descriptor_type] + descriptor = { + "name": None, + "type": descriptor_type, + "display_name": instrument.name, + "description": instrument.description, + "labels": [], + } + for key, value in record.labels: + if isinstance(value, str): + descriptor["labels"].append( + LabelDescriptor(key=key, value_type="STRING") + ) + elif isinstance(value, bool): + descriptor["labels"].append( + LabelDescriptor(key=key, value_type="BOOL") + ) + elif isinstance(value, int): + descriptor["labels"].append( + LabelDescriptor(key=key, value_type="INT64") + ) + else: + logger.warning( + "Label value %s is not a string, bool or integer", value + ) + if isinstance(record.aggregator, SumAggregator): + descriptor["metric_kind"] = MetricDescriptor.MetricKind.GAUGE + else: + logger.warning( + "Unsupported aggregation type %s, ignoring it", + type(record.aggregator).__name__, + ) + return None + if instrument.value_type == int: + descriptor["value_type"] = MetricDescriptor.ValueType.INT64 + elif instrument.value_type == float: + descriptor["value_type"] = MetricDescriptor.ValueType.DOUBLE + proto_descriptor = MetricDescriptor(**descriptor) + try: + descriptor = self.client.create_metric_descriptor( + self.project_name, proto_descriptor + ) + # pylint: disable=broad-except + except Exception as ex: + logger.error( + "Failed to create metric descriptor %s", + proto_descriptor, + exc_info=ex, + ) + return None + self._metric_descriptors[descriptor_type] = descriptor + return descriptor + + def export( + self, metric_records: Sequence[MetricRecord] + ) -> "MetricsExportResult": + all_series = [] + for record in metric_records: + instrument = record.instrument + metric_descriptor = self._get_metric_descriptor(record) + if not metric_descriptor: + continue + + series = TimeSeries() + self._add_resource_info(series) + series.metric.type = metric_descriptor.type + for key, value in record.labels: + series.metric.labels[key] = str(value) + + point = series.points.add() + if instrument.value_type == int: + point.value.int64_value = record.aggregator.checkpoint + elif instrument.value_type == float: + point.value.double_value = record.aggregator.checkpoint + seconds, nanos = divmod( + record.aggregator.last_update_timestamp, 1e9 + ) + + # Cloud Monitoring API allows, for any combination of labels and + # metric name, one update per WRITE_INTERVAL seconds + updated_key = (metric_descriptor.type, record.labels) + last_updated_seconds = self._last_updated.get(updated_key, 0) + if seconds <= last_updated_seconds + WRITE_INTERVAL: + continue + self._last_updated[updated_key] = seconds + point.interval.end_time.seconds = int(seconds) + point.interval.end_time.nanos = int(nanos) + all_series.append(series) + try: + self._batch_write(all_series) + # pylint: disable=broad-except + except Exception as ex: + logger.error( + "Error while writing to Cloud Monitoring", exc_info=ex + ) + return MetricsExportResult.FAILURE + return MetricsExportResult.SUCCESS diff --git a/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/version.py b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/version.py new file mode 100644 index 0000000000..3096170ce8 --- /dev/null +++ b/ext/opentelemetry-exporter-cloud-monitoring/src/opentelemetry/exporter/cloud_monitoring/version.py @@ -0,0 +1,15 @@ +# Copyright OpenTelemetry Authors +# +# 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. + +__version__ = "0.10.dev0" diff --git a/opentelemetry-auto-instrumentation/tests/__init__.py b/ext/opentelemetry-exporter-cloud-monitoring/tests/__init__.py similarity index 100% rename from opentelemetry-auto-instrumentation/tests/__init__.py rename to ext/opentelemetry-exporter-cloud-monitoring/tests/__init__.py diff --git a/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py b/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py new file mode 100644 index 0000000000..ccc90d2c2f --- /dev/null +++ b/ext/opentelemetry-exporter-cloud-monitoring/tests/test_cloud_monitoring.py @@ -0,0 +1,291 @@ +# Copyright OpenTelemetry Authors +# +# 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 unittest import mock + +from google.api.label_pb2 import LabelDescriptor +from google.api.metric_pb2 import MetricDescriptor +from google.cloud.monitoring_v3.proto.metric_pb2 import TimeSeries + +from opentelemetry.exporter.cloud_monitoring import ( + MAX_BATCH_WRITE, + WRITE_INTERVAL, + CloudMonitoringMetricsExporter, +) +from opentelemetry.sdk.metrics.export import MetricRecord +from opentelemetry.sdk.metrics.export.aggregate import SumAggregator + + +class UnsupportedAggregator: + pass + + +class MockMetric: + def __init__(self, name="name", description="description", value_type=int): + self.name = name + self.description = description + self.value_type = value_type + + +# pylint: disable=protected-access +# pylint can't deal with ProtoBuf object members +# pylint: disable=no-member + + +class TestCloudMonitoringMetricsExporter(unittest.TestCase): + def setUp(self): + self.client_patcher = mock.patch( + "opentelemetry.exporter.cloud_monitoring.MetricServiceClient" + ) + self.client_patcher.start() + self.project_id = "PROJECT" + self.project_name = "PROJECT_NAME" + + def tearDown(self): + self.client_patcher.stop() + + def test_constructor_default(self): + exporter = CloudMonitoringMetricsExporter(self.project_id) + self.assertEqual(exporter.project_id, self.project_id) + + def test_constructor_explicit(self): + client = mock.Mock() + exporter = CloudMonitoringMetricsExporter( + self.project_id, client=client + ) + + self.assertIs(exporter.client, client) + self.assertEqual(exporter.project_id, self.project_id) + + def test_batch_write(self): + client = mock.Mock() + exporter = CloudMonitoringMetricsExporter( + project_id=self.project_id, client=client + ) + exporter.project_name = self.project_name + exporter._batch_write(range(2 * MAX_BATCH_WRITE + 1)) + client.create_time_series.assert_has_calls( + [ + mock.call(self.project_name, range(MAX_BATCH_WRITE)), + mock.call( + self.project_name, + range(MAX_BATCH_WRITE, 2 * MAX_BATCH_WRITE), + ), + mock.call( + self.project_name, + range(2 * MAX_BATCH_WRITE, 2 * MAX_BATCH_WRITE + 1), + ), + ] + ) + + exporter._batch_write(range(MAX_BATCH_WRITE)) + client.create_time_series.assert_has_calls( + [mock.call(self.project_name, range(MAX_BATCH_WRITE))] + ) + + exporter._batch_write(range(MAX_BATCH_WRITE - 1)) + client.create_time_series.assert_has_calls( + [mock.call(self.project_name, range(MAX_BATCH_WRITE - 1))] + ) + + def test_get_metric_descriptor(self): + client = mock.Mock() + exporter = CloudMonitoringMetricsExporter( + project_id=self.project_id, client=client + ) + exporter.project_name = self.project_name + + self.assertIsNone( + exporter._get_metric_descriptor( + MetricRecord(MockMetric(), (), UnsupportedAggregator()) + ) + ) + + record = MetricRecord( + MockMetric(), (("label1", "value1"),), SumAggregator(), + ) + metric_descriptor = exporter._get_metric_descriptor(record) + client.create_metric_descriptor.assert_called_with( + self.project_name, + MetricDescriptor( + **{ + "name": None, + "type": "custom.googleapis.com/OpenTelemetry/name", + "display_name": "name", + "description": "description", + "labels": [ + LabelDescriptor(key="label1", value_type="STRING") + ], + "metric_kind": "GAUGE", + "value_type": "INT64", + } + ), + ) + + # Getting a cached metric descriptor shouldn't use another call + cached_metric_descriptor = exporter._get_metric_descriptor(record) + self.assertEqual(client.create_metric_descriptor.call_count, 1) + self.assertEqual(metric_descriptor, cached_metric_descriptor) + + # Drop labels with values that aren't string, int or bool + exporter._get_metric_descriptor( + MetricRecord( + MockMetric(name="name2", value_type=float), + ( + ("label1", "value1"), + ("label2", dict()), + ("label3", 3), + ("label4", False), + ), + SumAggregator(), + ) + ) + client.create_metric_descriptor.assert_called_with( + self.project_name, + MetricDescriptor( + **{ + "name": None, + "type": "custom.googleapis.com/OpenTelemetry/name2", + "display_name": "name2", + "description": "description", + "labels": [ + LabelDescriptor(key="label1", value_type="STRING"), + LabelDescriptor(key="label3", value_type="INT64"), + LabelDescriptor(key="label4", value_type="BOOL"), + ], + "metric_kind": "GAUGE", + "value_type": "DOUBLE", + } + ), + ) + + def test_export(self): + client = mock.Mock() + exporter = CloudMonitoringMetricsExporter( + project_id=self.project_id, client=client + ) + exporter.project_name = self.project_name + + exporter.export( + [ + MetricRecord( + MockMetric(), + (("label1", "value1"),), + UnsupportedAggregator(), + ) + ] + ) + client.create_time_series.assert_not_called() + + client.create_metric_descriptor.return_value = MetricDescriptor( + **{ + "name": None, + "type": "custom.googleapis.com/OpenTelemetry/name", + "display_name": "name", + "description": "description", + "labels": [ + LabelDescriptor(key="label1", value_type="STRING"), + LabelDescriptor(key="label2", value_type="INT64"), + ], + "metric_kind": "GAUGE", + "value_type": "DOUBLE", + } + ) + + sum_agg_one = SumAggregator() + sum_agg_one.checkpoint = 1 + sum_agg_one.last_update_timestamp = (WRITE_INTERVAL + 1) * 1e9 + exporter.export( + [ + MetricRecord( + MockMetric(), + (("label1", "value1"), ("label2", 1),), + sum_agg_one, + ), + MetricRecord( + MockMetric(), + (("label1", "value2"), ("label2", 2),), + sum_agg_one, + ), + ] + ) + series1 = TimeSeries() + series1.metric.type = "custom.googleapis.com/OpenTelemetry/name" + series1.metric.labels["label1"] = "value1" + series1.metric.labels["label2"] = "1" + point = series1.points.add() + point.value.int64_value = 1 + point.interval.end_time.seconds = WRITE_INTERVAL + 1 + point.interval.end_time.nanos = 0 + + series2 = TimeSeries() + series2.metric.type = "custom.googleapis.com/OpenTelemetry/name" + series2.metric.labels["label1"] = "value2" + series2.metric.labels["label2"] = "2" + point = series2.points.add() + point.value.int64_value = 1 + point.interval.end_time.seconds = WRITE_INTERVAL + 1 + point.interval.end_time.nanos = 0 + client.create_time_series.assert_has_calls( + [mock.call(self.project_name, [series1, series2])] + ) + + # Attempting to export too soon after another export with the exact + # same labels leads to it being dropped + + sum_agg_two = SumAggregator() + sum_agg_two.checkpoint = 1 + sum_agg_two.last_update_timestamp = (WRITE_INTERVAL + 2) * 1e9 + exporter.export( + [ + MetricRecord( + MockMetric(), + (("label1", "value1"), ("label2", 1),), + sum_agg_two, + ), + MetricRecord( + MockMetric(), + (("label1", "value2"), ("label2", 2),), + sum_agg_two, + ), + ] + ) + self.assertEqual(client.create_time_series.call_count, 1) + + # But exporting with different labels is fine + sum_agg_two.checkpoint = 2 + exporter.export( + [ + MetricRecord( + MockMetric(), + (("label1", "changed_label"), ("label2", 2),), + sum_agg_two, + ), + ] + ) + series3 = TimeSeries() + series3.metric.type = "custom.googleapis.com/OpenTelemetry/name" + series3.metric.labels["label1"] = "changed_label" + series3.metric.labels["label2"] = "2" + point = series3.points.add() + point.value.int64_value = 2 + point.interval.end_time.seconds = WRITE_INTERVAL + 2 + point.interval.end_time.nanos = 0 + client.create_time_series.assert_has_calls( + [ + mock.call(self.project_name, [series1, series2]), + mock.call(self.project_name, [series3]), + ] + ) diff --git a/ext/opentelemetry-exporter-cloud-trace/setup.cfg b/ext/opentelemetry-exporter-cloud-trace/setup.cfg index df6c2ce587..41ffc4116a 100644 --- a/ext/opentelemetry-exporter-cloud-trace/setup.cfg +++ b/ext/opentelemetry-exporter-cloud-trace/setup.cfg @@ -45,3 +45,6 @@ install_requires = [options.packages.find] where = src + +[options.extras_require] +test = diff --git a/ext/opentelemetry-exporter-cloud-trace/src/opentelemetry/exporter/cloud_trace/version.py b/ext/opentelemetry-exporter-cloud-trace/src/opentelemetry/exporter/cloud_trace/version.py index f83f20e7ba..3096170ce8 100644 --- a/ext/opentelemetry-exporter-cloud-trace/src/opentelemetry/exporter/cloud_trace/version.py +++ b/ext/opentelemetry-exporter-cloud-trace/src/opentelemetry/exporter/cloud_trace/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-aiohttp-client/setup.cfg b/ext/opentelemetry-ext-aiohttp-client/setup.cfg index af4839fc3d..431e92273e 100644 --- a/ext/opentelemetry-ext-aiohttp-client/setup.cfg +++ b/ext/opentelemetry-ext-aiohttp-client/setup.cfg @@ -39,8 +39,12 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api >= 0.9.dev0 + opentelemetry-api >= 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 aiohttp ~= 3.0 [options.packages.find] where = src + +[options.extras_require] +test = diff --git a/ext/opentelemetry-ext-aiohttp-client/src/opentelemetry/ext/aiohttp_client/__init__.py b/ext/opentelemetry-ext-aiohttp-client/src/opentelemetry/ext/aiohttp_client/__init__.py index 77dadbd645..a05e7c0601 100644 --- a/ext/opentelemetry-ext-aiohttp-client/src/opentelemetry/ext/aiohttp_client/__init__.py +++ b/ext/opentelemetry-ext-aiohttp-client/src/opentelemetry/ext/aiohttp_client/__init__.py @@ -52,38 +52,11 @@ def strip_query_params(url: yarl.URL) -> str: from opentelemetry import context as context_api from opentelemetry import propagators, trace from opentelemetry.ext.aiohttp_client.version import __version__ +from opentelemetry.instrumentation.utils import http_status_to_canonical_code from opentelemetry.trace import SpanKind from opentelemetry.trace.status import Status, StatusCanonicalCode -# TODO: refactor this code to some common utility -def http_status_to_canonical_code(status: int) -> StatusCanonicalCode: - # pylint:disable=too-many-branches,too-many-return-statements - if status < 100: - return StatusCanonicalCode.UNKNOWN - if status <= 399: - return StatusCanonicalCode.OK - if status <= 499: - if status == 401: # HTTPStatus.UNAUTHORIZED: - return StatusCanonicalCode.UNAUTHENTICATED - if status == 403: # HTTPStatus.FORBIDDEN: - return StatusCanonicalCode.PERMISSION_DENIED - if status == 404: # HTTPStatus.NOT_FOUND: - return StatusCanonicalCode.NOT_FOUND - if status == 429: # HTTPStatus.TOO_MANY_REQUESTS: - return StatusCanonicalCode.RESOURCE_EXHAUSTED - return StatusCanonicalCode.INVALID_ARGUMENT - if status <= 599: - if status == 501: # HTTPStatus.NOT_IMPLEMENTED: - return StatusCanonicalCode.UNIMPLEMENTED - if status == 503: # HTTPStatus.SERVICE_UNAVAILABLE: - return StatusCanonicalCode.UNAVAILABLE - if status == 504: # HTTPStatus.GATEWAY_TIMEOUT: - return StatusCanonicalCode.DEADLINE_EXCEEDED - return StatusCanonicalCode.INTERNAL - return StatusCanonicalCode.UNKNOWN - - def url_path_span_name(params: aiohttp.TraceRequestStartParams) -> str: """Extract a span name from the request URL path. @@ -172,7 +145,7 @@ async def on_request_start( ) trace_config_ctx.token = context_api.attach( - trace.propagation.set_span_in_context(trace_config_ctx.span) + trace.set_span_in_context(trace_config_ctx.span) ) propagators.inject(type(params.headers).__setitem__, params.headers) diff --git a/ext/opentelemetry-ext-aiohttp-client/src/opentelemetry/ext/aiohttp_client/version.py b/ext/opentelemetry-ext-aiohttp-client/src/opentelemetry/ext/aiohttp_client/version.py index 410979c2eb..63ed5cd81a 100644 --- a/ext/opentelemetry-ext-aiohttp-client/src/opentelemetry/ext/aiohttp_client/version.py +++ b/ext/opentelemetry-ext-aiohttp-client/src/opentelemetry/ext/aiohttp_client/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-aiohttp-client/tests/test_aiohttp_client_integration.py b/ext/opentelemetry-ext-aiohttp-client/tests/test_aiohttp_client_integration.py index 8cf5048e58..8db20c227e 100644 --- a/ext/opentelemetry-ext-aiohttp-client/tests/test_aiohttp_client_integration.py +++ b/ext/opentelemetry-ext-aiohttp-client/tests/test_aiohttp_client_integration.py @@ -98,43 +98,6 @@ async def default_handler(request): loop = asyncio.get_event_loop() return loop.run_until_complete(do_request()) - def test_http_status_to_canonical_code(self): - for status_code, expected in ( - (HTTPStatus.OK, StatusCanonicalCode.OK), - (HTTPStatus.ACCEPTED, StatusCanonicalCode.OK), - (HTTPStatus.IM_USED, StatusCanonicalCode.OK), - (HTTPStatus.MULTIPLE_CHOICES, StatusCanonicalCode.OK), - (HTTPStatus.BAD_REQUEST, StatusCanonicalCode.INVALID_ARGUMENT), - (HTTPStatus.UNAUTHORIZED, StatusCanonicalCode.UNAUTHENTICATED), - (HTTPStatus.FORBIDDEN, StatusCanonicalCode.PERMISSION_DENIED), - (HTTPStatus.NOT_FOUND, StatusCanonicalCode.NOT_FOUND), - ( - HTTPStatus.UNPROCESSABLE_ENTITY, - StatusCanonicalCode.INVALID_ARGUMENT, - ), - ( - HTTPStatus.TOO_MANY_REQUESTS, - StatusCanonicalCode.RESOURCE_EXHAUSTED, - ), - (HTTPStatus.NOT_IMPLEMENTED, StatusCanonicalCode.UNIMPLEMENTED), - (HTTPStatus.SERVICE_UNAVAILABLE, StatusCanonicalCode.UNAVAILABLE), - ( - HTTPStatus.GATEWAY_TIMEOUT, - StatusCanonicalCode.DEADLINE_EXCEEDED, - ), - ( - HTTPStatus.HTTP_VERSION_NOT_SUPPORTED, - StatusCanonicalCode.INTERNAL, - ), - (600, StatusCanonicalCode.UNKNOWN), - (99, StatusCanonicalCode.UNKNOWN), - ): - with self.subTest(status_code=status_code): - actual = opentelemetry.ext.aiohttp_client.http_status_to_canonical_code( - int(status_code) - ) - self.assertEqual(actual, expected, status_code) - def test_status_codes(self): for status_code, span_status in ( (HTTPStatus.OK, StatusCanonicalCode.OK), diff --git a/ext/opentelemetry-ext-asgi/setup.cfg b/ext/opentelemetry-ext-asgi/setup.cfg index 2be30ce41c..ab0e3e7f47 100644 --- a/ext/opentelemetry-ext-asgi/setup.cfg +++ b/ext/opentelemetry-ext-asgi/setup.cfg @@ -39,12 +39,13 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 asgiref ~= 3.0 [options.extras_require] test = - opentelemetry-ext-testutil + opentelemetry-test [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-asgi/src/opentelemetry/ext/asgi/__init__.py b/ext/opentelemetry-ext-asgi/src/opentelemetry/ext/asgi/__init__.py index 69c30848da..43b8804c24 100644 --- a/ext/opentelemetry-ext-asgi/src/opentelemetry/ext/asgi/__init__.py +++ b/ext/opentelemetry-ext-asgi/src/opentelemetry/ext/asgi/__init__.py @@ -22,11 +22,13 @@ import typing import urllib from functools import wraps +from typing import Tuple from asgiref.compatibility import guarantee_single_callable from opentelemetry import context, propagators, trace from opentelemetry.ext.asgi.version import __version__ # noqa +from opentelemetry.instrumentation.utils import http_status_to_canonical_code from opentelemetry.trace.status import Status, StatusCanonicalCode @@ -44,37 +46,6 @@ def get_header_from_scope(scope: dict, header_name: str) -> typing.List[str]: ] -def http_status_to_canonical_code(code: int, allow_redirect: bool = True): - # pylint:disable=too-many-branches,too-many-return-statements - if code < 100: - return StatusCanonicalCode.UNKNOWN - if code <= 299: - return StatusCanonicalCode.OK - if code <= 399: - if allow_redirect: - return StatusCanonicalCode.OK - return StatusCanonicalCode.DEADLINE_EXCEEDED - if code <= 499: - if code == 401: # HTTPStatus.UNAUTHORIZED: - return StatusCanonicalCode.UNAUTHENTICATED - if code == 403: # HTTPStatus.FORBIDDEN: - return StatusCanonicalCode.PERMISSION_DENIED - if code == 404: # HTTPStatus.NOT_FOUND: - return StatusCanonicalCode.NOT_FOUND - if code == 429: # HTTPStatus.TOO_MANY_REQUESTS: - return StatusCanonicalCode.RESOURCE_EXHAUSTED - return StatusCanonicalCode.INVALID_ARGUMENT - if code <= 599: - if code == 501: # HTTPStatus.NOT_IMPLEMENTED: - return StatusCanonicalCode.UNIMPLEMENTED - if code == 503: # HTTPStatus.SERVICE_UNAVAILABLE: - return StatusCanonicalCode.UNAVAILABLE - if code == 504: # HTTPStatus.GATEWAY_TIMEOUT: - return StatusCanonicalCode.DEADLINE_EXCEEDED - return StatusCanonicalCode.INTERNAL - return StatusCanonicalCode.UNKNOWN - - def collect_request_attributes(scope): """Collects HTTP request attributes from the ASGI scope and returns a dictionary to be used as span creation attributes.""" @@ -134,11 +105,19 @@ def set_status_code(span, status_code): span.set_status(Status(http_status_to_canonical_code(status_code))) -def get_default_span_name(scope): - """Default implementation for name_callback""" +def get_default_span_details(scope: dict) -> Tuple[str, dict]: + """Default implementation for span_details_callback + + Args: + scope: the asgi scope dictionary + + Returns: + a tuple of the span, and any attributes to attach to the + span. + """ method_or_path = scope.get("method") or scope.get("path") - return method_or_path + return method_or_path, {} class OpenTelemetryMiddleware: @@ -149,15 +128,18 @@ class OpenTelemetryMiddleware: Args: app: The ASGI application callable to forward requests to. - name_callback: Callback which calculates a generic span name for an - incoming HTTP request based on the ASGI scope. - Optional: Defaults to get_default_span_name. + span_details_callback: Callback which should return a string + and a tuple, representing the desired span name and a + dictionary with any additional span attributes to set. + Optional: Defaults to get_default_span_details. """ - def __init__(self, app, name_callback=None): + def __init__(self, app, span_details_callback=None): self.app = guarantee_single_callable(app) self.tracer = trace.get_tracer(__name__, __version__) - self.name_callback = name_callback or get_default_span_name + self.span_details_callback = ( + span_details_callback or get_default_span_details + ) async def __call__(self, scope, receive, send): """The ASGI application @@ -173,13 +155,15 @@ async def __call__(self, scope, receive, send): token = context.attach( propagators.extract(get_header_from_scope, scope) ) - span_name = self.name_callback(scope) + span_name, additional_attributes = self.span_details_callback(scope) + attributes = collect_request_attributes(scope) + attributes.update(additional_attributes) try: with self.tracer.start_as_current_span( span_name + " asgi", kind=trace.SpanKind.SERVER, - attributes=collect_request_attributes(scope), + attributes=attributes, ): @wraps(receive) diff --git a/ext/opentelemetry-ext-asgi/src/opentelemetry/ext/asgi/version.py b/ext/opentelemetry-ext-asgi/src/opentelemetry/ext/asgi/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-asgi/src/opentelemetry/ext/asgi/version.py +++ b/ext/opentelemetry-ext-asgi/src/opentelemetry/ext/asgi/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-asgi/tests/test_asgi_middleware.py b/ext/opentelemetry-ext-asgi/tests/test_asgi_middleware.py index edc90444a7..05aa84b3c4 100644 --- a/ext/opentelemetry-ext-asgi/tests/test_asgi_middleware.py +++ b/ext/opentelemetry-ext-asgi/tests/test_asgi_middleware.py @@ -176,9 +176,8 @@ def test_override_span_name(self): """Test that span_names can be overwritten by our callback function.""" span_name = "Dymaxion" - # pylint:disable=unused-argument - def get_predefined_span_name(scope): - return span_name + def get_predefined_span_details(_): + return span_name, {} def update_expected_span_name(expected): for entry in expected: @@ -188,7 +187,7 @@ def update_expected_span_name(expected): return expected app = otel_asgi.OpenTelemetryMiddleware( - simple_asgi, name_callback=get_predefined_span_name + simple_asgi, span_details_callback=get_predefined_span_details ) self.seed_app(app) self.send_default_request() diff --git a/ext/opentelemetry-ext-boto/CHANGELOG.md b/ext/opentelemetry-ext-boto/CHANGELOG.md index 3e04402cea..896a782491 100644 --- a/ext/opentelemetry-ext-boto/CHANGELOG.md +++ b/ext/opentelemetry-ext-boto/CHANGELOG.md @@ -2,4 +2,8 @@ ## Unreleased +## 0.9b0 + +Released 2020-06-10 + - Initial release diff --git a/ext/opentelemetry-ext-boto/setup.cfg b/ext/opentelemetry-ext-boto/setup.cfg index 529e79be99..53e26fcf14 100644 --- a/ext/opentelemetry-ext-boto/setup.cfg +++ b/ext/opentelemetry-ext-boto/setup.cfg @@ -41,14 +41,14 @@ package_dir= packages=find_namespace: install_requires = boto ~= 2.0 - opentelemetry-api == 0.9.dev0 - opentelemetry-auto-instrumentation == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 [options.extras_require] test = boto~=2.0 moto~=1.0 - opentelemetry-test == 0.9.dev0 + opentelemetry-test == 0.10.dev0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-boto/src/opentelemetry/ext/boto/__init__.py b/ext/opentelemetry-ext-boto/src/opentelemetry/ext/boto/__init__.py index fa66fda61d..97d4922658 100644 --- a/ext/opentelemetry-ext-boto/src/opentelemetry/ext/boto/__init__.py +++ b/ext/opentelemetry-ext-boto/src/opentelemetry/ext/boto/__init__.py @@ -15,7 +15,7 @@ Instrument `Boto`_ to trace service requests. There are two options for instrumenting code. The first option is to use the -``opentelemetry-auto-instrumentation`` executable which will automatically +``opentelemetry-instrument`` executable which will automatically instrument your Boto client. The second is to programmatically enable instrumentation via the following code: @@ -50,8 +50,8 @@ from boto.connection import AWSAuthConnection, AWSQueryConnection from wrapt import ObjectProxy, wrap_function_wrapper -from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor from opentelemetry.ext.boto.version import __version__ +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.trace import SpanKind, get_tracer logger = logging.getLogger(__name__) diff --git a/ext/opentelemetry-ext-boto/src/opentelemetry/ext/boto/version.py b/ext/opentelemetry-ext-boto/src/opentelemetry/ext/boto/version.py index bcf6a35777..6d4fefa599 100644 --- a/ext/opentelemetry-ext-boto/src/opentelemetry/ext/boto/version.py +++ b/ext/opentelemetry-ext-boto/src/opentelemetry/ext/boto/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.8.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-boto/tests/test_boto_instrumentation.py b/ext/opentelemetry-ext-boto/tests/test_boto_instrumentation.py index 492fac5a88..a629b10870 100644 --- a/ext/opentelemetry-ext-boto/tests/test_boto_instrumentation.py +++ b/ext/opentelemetry-ext-boto/tests/test_boto_instrumentation.py @@ -19,13 +19,13 @@ import boto.elasticache import boto.s3 import boto.sts - from moto import ( # pylint: disable=import-error mock_ec2_deprecated, mock_lambda_deprecated, mock_s3_deprecated, mock_sts_deprecated, ) + from opentelemetry.ext.boto import BotoInstrumentor from opentelemetry.test.test_base import TestBase diff --git a/ext/opentelemetry-ext-botocore/CHANGELOG.md b/ext/opentelemetry-ext-botocore/CHANGELOG.md new file mode 100644 index 0000000000..896a782491 --- /dev/null +++ b/ext/opentelemetry-ext-botocore/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## Unreleased + +## 0.9b0 + +Released 2020-06-10 + +- Initial release diff --git a/ext/opentelemetry-ext-botocore/LICENSE b/ext/opentelemetry-ext-botocore/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/ext/opentelemetry-ext-botocore/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/ext/opentelemetry-ext-botocore/MANIFEST.in b/ext/opentelemetry-ext-botocore/MANIFEST.in new file mode 100644 index 0000000000..aed3e33273 --- /dev/null +++ b/ext/opentelemetry-ext-botocore/MANIFEST.in @@ -0,0 +1,9 @@ +graft src +graft tests +global-exclude *.pyc +global-exclude *.pyo +global-exclude __pycache__/* +include CHANGELOG.md +include MANIFEST.in +include README.rst +include LICENSE diff --git a/ext/opentelemetry-ext-botocore/README.rst b/ext/opentelemetry-ext-botocore/README.rst new file mode 100644 index 0000000000..0b50819d32 --- /dev/null +++ b/ext/opentelemetry-ext-botocore/README.rst @@ -0,0 +1,23 @@ +OpenTelemetry Botocore Tracing +============================== + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/opentelemetry-ext-botocore.svg + :target: https://pypi.org/project/opentelemetry-ext-botocore/ + +This library allows tracing requests made by the Botocore library. + +Installation +------------ + +:: + + pip install opentelemetry-ext-botocore + + +References +---------- + +* `OpenTelemetry Botocore Tracing `_ +* `OpenTelemetry Project `_ diff --git a/ext/opentelemetry-ext-botocore/setup.cfg b/ext/opentelemetry-ext-botocore/setup.cfg new file mode 100644 index 0000000000..59cb7f2271 --- /dev/null +++ b/ext/opentelemetry-ext-botocore/setup.cfg @@ -0,0 +1,57 @@ +# Copyright The OpenTelemetry Authors +# +# 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. +# +[metadata] +name = opentelemetry-ext-botocore +description = Botocore tracing for OpenTelemetry +long_description = file: README.rst +long_description_content_type = text/x-rst +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +url = https://github.com/open-telemetry/opentelemetry-python/tree/master/ext/opentelemetry-ext-botocore +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + +[options] +python_requires = >=3.4 +package_dir= + =src +packages=find_namespace: +install_requires = + botocore ~= 1.0 + opentelemetry-api == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 + +[options.extras_require] +test = + moto ~= 1.0 + opentelemetry-test == 0.10.dev0 + +[options.packages.find] +where = src + +[options.entry_points] +opentelemetry_instrumentor = + django = opentelemetry.ext.botocore:BotoCoreInstrumentor diff --git a/ext/opentelemetry-ext-botocore/setup.py b/ext/opentelemetry-ext-botocore/setup.py new file mode 100644 index 0000000000..35b47b1b00 --- /dev/null +++ b/ext/opentelemetry-ext-botocore/setup.py @@ -0,0 +1,26 @@ +# Copyright The OpenTelemetry Authors +# +# 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 setuptools + +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join( + BASE_DIR, "src", "opentelemetry", "ext", "botocore", "version.py" +) +PACKAGE_INFO = {} +with open(VERSION_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup(version=PACKAGE_INFO["__version__"]) diff --git a/ext/opentelemetry-ext-botocore/src/opentelemetry/ext/botocore/__init__.py b/ext/opentelemetry-ext-botocore/src/opentelemetry/ext/botocore/__init__.py new file mode 100644 index 0000000000..97615d974f --- /dev/null +++ b/ext/opentelemetry-ext-botocore/src/opentelemetry/ext/botocore/__init__.py @@ -0,0 +1,209 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +""" +Instrument `Botocore`_ to trace service requests. + +There are two options for instrumenting code. The first option is to use the +``opentelemetry-instrument`` executable which will automatically +instrument your Botocore client. The second is to programmatically enable +instrumentation via the following code: + +.. _Botocore: https://pypi.org/project/botocore/ + +Usage +----- + +.. code:: python + + from opentelemetry import trace + from opentelemetry.ext.botocore import BotocoreInstrumentor + from opentelemetry.sdk.trace import TracerProvider + import botocore + + trace.set_tracer_provider(TracerProvider()) + + # Instrument Botocore + BotocoreInstrumentor().instrument( + tracer_provider=trace.get_tracer_provider() + ) + + # This will create a span with Botocore-specific attributes + session = botocore.session.get_session() + session.set_credentials( + access_key="access-key", secret_key="secret-key" + ) + ec2 = self.session.create_client("ec2", region_name="us-west-2") + ec2.describe_instances() + +API +--- +""" + +import logging + +from botocore.client import BaseClient +from wrapt import ObjectProxy, wrap_function_wrapper + +from opentelemetry.ext.botocore.version import __version__ +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.trace import SpanKind, get_tracer + +logger = logging.getLogger(__name__) + + +class BotocoreInstrumentor(BaseInstrumentor): + """A instrumentor for Botocore + + See `BaseInstrumentor` + """ + + def _instrument(self, **kwargs): + + # FIXME should the tracer provider be accessed via Configuration + # instead? + # pylint: disable=attribute-defined-outside-init + self._tracer = get_tracer( + __name__, __version__, kwargs.get("tracer_provider") + ) + + wrap_function_wrapper( + "botocore.client", + "BaseClient._make_api_call", + self._patched_api_call, + ) + + def _uninstrument(self, **kwargs): + unwrap(BaseClient, "_make_api_call") + + def _patched_api_call(self, original_func, instance, args, kwargs): + + endpoint_name = deep_getattr(instance, "_endpoint._endpoint_prefix") + + with self._tracer.start_as_current_span( + "{}.command".format(endpoint_name), kind=SpanKind.CONSUMER, + ) as span: + + operation = None + if args: + operation = args[0] + span.resource = "%s.%s" % (endpoint_name, operation.lower()) + + else: + span.resource = endpoint_name + + add_span_arg_tags( + span, + endpoint_name, + args, + ("action", "params", "path", "verb"), + {"params", "path", "verb"}, + ) + + region_name = deep_getattr(instance, "meta.region_name") + + meta = { + "aws.agent": "botocore", + "aws.operation": operation, + "aws.region": region_name, + } + for key, value in meta.items(): + span.set_attribute(key, value) + + result = original_func(*args, **kwargs) + + span.set_attribute( + "http.status_code", + result["ResponseMetadata"]["HTTPStatusCode"], + ) + span.set_attribute( + "retry_attempts", result["ResponseMetadata"]["RetryAttempts"], + ) + + return result + + +def unwrap(obj, attr): + function = getattr(obj, attr, None) + if ( + function + and isinstance(function, ObjectProxy) + and hasattr(function, "__wrapped__") + ): + setattr(obj, attr, function.__wrapped__) + + +def add_span_arg_tags(span, endpoint_name, args, args_names, args_traced): + def truncate_arg_value(value, max_len=1024): + """Truncate values which are bytes and greater than `max_len`. + Useful for parameters like "Body" in `put_object` operations. + """ + if isinstance(value, bytes) and len(value) > max_len: + return b"..." + + return value + + if endpoint_name not in {"kms", "sts"}: + tags = dict( + (name, value) + for (name, value) in zip(args_names, args) + if name in args_traced + ) + tags = flatten_dict(tags) + for key, value in { + k: truncate_arg_value(v) + for k, v in tags.items() + if k not in {"s3": ["params.Body"]}.get(endpoint_name, []) + }.items(): + span.set_attribute(key, value) + + +def flatten_dict(dict_, sep=".", prefix=""): + """ + Returns a normalized dict of depth 1 with keys in order of embedding + """ + # adapted from https://stackoverflow.com/a/19647596 + return ( + { + prefix + sep + k if prefix else k: v + for kk, vv in dict_.items() + for k, v in flatten_dict(vv, sep, kk).items() + } + if isinstance(dict_, dict) + else {prefix: dict_} + ) + + +def deep_getattr(obj, attr_string, default=None): + """ + Returns the attribute of ``obj`` at the dotted path given by + ``attr_string``, if no such attribute is reachable, returns ``default``. + + >>> deep_getattr(cass, "cluster") + >> deep_getattr(cass, "cluster.metadata.partitioner") + u"org.apache.cassandra.dht.Murmur3Partitioner" + + >>> deep_getattr(cass, "i.dont.exist", default="default") + "default" + """ + attrs = attr_string.split(".") + for attr in attrs: + try: + obj = getattr(obj, attr) + except AttributeError: + return default + + return obj diff --git a/ext/opentelemetry-ext-botocore/src/opentelemetry/ext/botocore/version.py b/ext/opentelemetry-ext-botocore/src/opentelemetry/ext/botocore/version.py new file mode 100644 index 0000000000..6d4fefa599 --- /dev/null +++ b/ext/opentelemetry-ext-botocore/src/opentelemetry/ext/botocore/version.py @@ -0,0 +1,15 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-botocore/tests/__init__.py b/ext/opentelemetry-ext-botocore/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ext/opentelemetry-ext-botocore/tests/test_botocore_instrumentation.py b/ext/opentelemetry-ext-botocore/tests/test_botocore_instrumentation.py new file mode 100644 index 0000000000..64d0c3d7b7 --- /dev/null +++ b/ext/opentelemetry-ext-botocore/tests/test_botocore_instrumentation.py @@ -0,0 +1,197 @@ +import botocore.session +from botocore.exceptions import ParamValidationError +from moto import ( # pylint: disable=import-error + mock_ec2, + mock_kinesis, + mock_kms, + mock_lambda, + mock_s3, + mock_sqs, +) + +from opentelemetry.ext.botocore import BotocoreInstrumentor +from opentelemetry.test.test_base import TestBase + + +def assert_span_http_status_code(span, code): + """Assert on the span"s "http.status_code" tag""" + tag = span.attributes["http.status_code"] + assert tag == code, "%r != %r" % (tag, code) + + +class TestBotocoreInstrumentor(TestBase): + """Botocore integration testsuite""" + + def setUp(self): + super().setUp() + BotocoreInstrumentor().instrument() + + self.session = botocore.session.get_session() + self.session.set_credentials( + access_key="access-key", secret_key="secret-key" + ) + + def tearDown(self): + super().tearDown() + BotocoreInstrumentor().uninstrument() + + @mock_ec2 + def test_traced_client(self): + ec2 = self.session.create_client("ec2", region_name="us-west-2") + + ec2.describe_instances() + + spans = self.memory_exporter.get_finished_spans() + assert spans + span = spans[0] + self.assertEqual(len(spans), 1) + self.assertEqual(span.attributes["aws.agent"], "botocore") + self.assertEqual(span.attributes["aws.region"], "us-west-2") + self.assertEqual(span.attributes["aws.operation"], "DescribeInstances") + assert_span_http_status_code(span, 200) + self.assertEqual(span.resource, "ec2.describeinstances") + self.assertEqual(span.name, "ec2.command") + + @mock_ec2 + def test_traced_client_analytics(self): + ec2 = self.session.create_client("ec2", region_name="us-west-2") + ec2.describe_instances() + + spans = self.memory_exporter.get_finished_spans() + assert spans + + @mock_s3 + def test_s3_client(self): + s3 = self.session.create_client("s3", region_name="us-west-2") + + s3.list_buckets() + s3.list_buckets() + + spans = self.memory_exporter.get_finished_spans() + assert spans + span = spans[0] + self.assertEqual(len(spans), 2) + self.assertEqual(span.attributes["aws.operation"], "ListBuckets") + assert_span_http_status_code(span, 200) + self.assertEqual(span.resource, "s3.listbuckets") + + # testing for span error + self.memory_exporter.get_finished_spans() + with self.assertRaises(ParamValidationError): + s3.list_objects(bucket="mybucket") + spans = self.memory_exporter.get_finished_spans() + assert spans + span = spans[2] + self.assertEqual(span.resource, "s3.listobjects") + + @mock_s3 + def test_s3_put(self): + params = dict(Key="foo", Bucket="mybucket", Body=b"bar") + s3 = self.session.create_client("s3", region_name="us-west-2") + s3.create_bucket(Bucket="mybucket") + s3.put_object(**params) + + spans = self.memory_exporter.get_finished_spans() + assert spans + span = spans[0] + self.assertEqual(len(spans), 2) + self.assertEqual(span.attributes["aws.operation"], "CreateBucket") + assert_span_http_status_code(span, 200) + self.assertEqual(span.resource, "s3.createbucket") + self.assertEqual(spans[1].attributes["aws.operation"], "PutObject") + self.assertEqual(spans[1].resource, "s3.putobject") + self.assertEqual(spans[1].attributes["params.Key"], str(params["Key"])) + self.assertEqual( + spans[1].attributes["params.Bucket"], str(params["Bucket"]) + ) + self.assertTrue("params.Body" not in spans[1].attributes.keys()) + + @mock_sqs + def test_sqs_client(self): + sqs = self.session.create_client("sqs", region_name="us-east-1") + + sqs.list_queues() + + spans = self.memory_exporter.get_finished_spans() + assert spans + span = spans[0] + self.assertEqual(len(spans), 1) + self.assertEqual(span.attributes["aws.region"], "us-east-1") + self.assertEqual(span.attributes["aws.operation"], "ListQueues") + assert_span_http_status_code(span, 200) + self.assertEqual(span.resource, "sqs.listqueues") + + @mock_kinesis + def test_kinesis_client(self): + kinesis = self.session.create_client( + "kinesis", region_name="us-east-1" + ) + + kinesis.list_streams() + + spans = self.memory_exporter.get_finished_spans() + assert spans + span = spans[0] + self.assertEqual(len(spans), 1) + self.assertEqual(span.attributes["aws.region"], "us-east-1") + self.assertEqual(span.attributes["aws.operation"], "ListStreams") + assert_span_http_status_code(span, 200) + self.assertEqual(span.resource, "kinesis.liststreams") + + @mock_kinesis + def test_unpatch(self): + kinesis = self.session.create_client( + "kinesis", region_name="us-east-1" + ) + + BotocoreInstrumentor().uninstrument() + + kinesis.list_streams() + spans = self.memory_exporter.get_finished_spans() + assert not spans, spans + + @mock_sqs + def test_double_patch(self): + sqs = self.session.create_client("sqs", region_name="us-east-1") + + BotocoreInstrumentor().instrument() + BotocoreInstrumentor().instrument() + + sqs.list_queues() + + spans = self.memory_exporter.get_finished_spans() + assert spans + self.assertEqual(len(spans), 1) + + @mock_lambda + def test_lambda_client(self): + lamb = self.session.create_client("lambda", region_name="us-east-1") + + lamb.list_functions() + + spans = self.memory_exporter.get_finished_spans() + assert spans + span = spans[0] + self.assertEqual(len(spans), 1) + self.assertEqual(span.attributes["aws.region"], "us-east-1") + self.assertEqual(span.attributes["aws.operation"], "ListFunctions") + assert_span_http_status_code(span, 200) + self.assertEqual(span.resource, "lambda.listfunctions") + + @mock_kms + def test_kms_client(self): + kms = self.session.create_client("kms", region_name="us-east-1") + + kms.list_keys(Limit=21) + + spans = self.memory_exporter.get_finished_spans() + assert spans + span = spans[0] + self.assertEqual(len(spans), 1) + self.assertEqual(span.attributes["aws.region"], "us-east-1") + self.assertEqual(span.attributes["aws.operation"], "ListKeys") + assert_span_http_status_code(span, 200) + self.assertEqual(span.resource, "kms.listkeys") + + # checking for protection on sts against security leak + self.assertTrue("params" not in span.attributes.keys()) diff --git a/ext/opentelemetry-ext-datadog/setup.cfg b/ext/opentelemetry-ext-datadog/setup.cfg index afdd739d12..2d663de89b 100644 --- a/ext/opentelemetry-ext-datadog/setup.cfg +++ b/ext/opentelemetry-ext-datadog/setup.cfg @@ -40,8 +40,11 @@ package_dir= packages=find_namespace: install_requires = ddtrace>=0.34.0 - opentelemetry-api==0.9.dev0 - opentelemetry-sdk==0.9.dev0 + opentelemetry-api==0.10.dev0 + opentelemetry-sdk==0.10.dev0 [options.packages.find] where = src + +[options.extras_require] +test = diff --git a/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/__init__.py b/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/__init__.py index 0c01cf7fba..85bdaea40a 100644 --- a/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/__init__.py +++ b/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/__init__.py @@ -16,13 +16,27 @@ The **OpenTelemetry Datadog Exporter** provides a span exporter from `OpenTelemetry`_ traces to `Datadog`_ by using the Datadog Agent. +Installation +------------ + +:: + + pip install opentelemetry-ext-datadog + + Usage ----- +The Datadog exporter provides a span processor that must be added along with the +exporter. In addition, a formatter is provided to handle propagation of trace +context between OpenTelemetry-instrumented and Datadog-instrumented services in +a distributed trace. + .. code:: python - from opentelemetry import trace + from opentelemetry import propagators, trace from opentelemetry.ext.datadog import DatadogExportSpanProcessor, DatadogSpanExporter + from opentelemetry.ext.datadog.propagator import DatadogFormat from opentelemetry.sdk.trace import TracerProvider trace.set_tracer_provider(TracerProvider()) @@ -35,13 +49,24 @@ span_processor = DatadogExportSpanProcessor(exporter) trace.get_tracer_provider().add_span_processor(span_processor) + # Optional: use Datadog format for propagation in distributed traces + propagators.set_global_httptextformat(DatadogFormat()) + with tracer.start_as_current_span("foo"): print("Hello world!") + +Examples +-------- + +The `docs/examples/datadog_exporter`_ includes examples for using the Datadog +exporter with OpenTelemetry instrumented applications. + API --- .. _Datadog: https://www.datadoghq.com/ .. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/ +.. _docs/examples/datadog_exporter: https://github.com/open-telemetry/opentelemetry-python/tree/master/docs/examples/datadog_exporter """ # pylint: disable=import-error diff --git a/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/exporter.py b/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/exporter.py index 35d0f98203..e267910a9d 100644 --- a/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/exporter.py +++ b/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/exporter.py @@ -33,6 +33,7 @@ DEFAULT_AGENT_URL = "http://localhost:8126" _INSTRUMENTATION_SPAN_TYPES = { "opentelemetry.ext.aiohttp-client": DatadogSpanTypes.HTTP, + "opentelemetry.ext.asgi": DatadogSpanTypes.WEB, "opentelemetry.ext.dbapi": DatadogSpanTypes.SQL, "opentelemetry.ext.django": DatadogSpanTypes.WEB, "opentelemetry.ext.flask": DatadogSpanTypes.WEB, @@ -40,6 +41,7 @@ "opentelemetry.ext.jinja2": DatadogSpanTypes.TEMPLATE, "opentelemetry.ext.mysql": DatadogSpanTypes.SQL, "opentelemetry.ext.psycopg2": DatadogSpanTypes.SQL, + "opentelemetry.ext.pymemcache": DatadogSpanTypes.CACHE, "opentelemetry.ext.pymongo": DatadogSpanTypes.MONGODB, "opentelemetry.ext.pymysql": DatadogSpanTypes.SQL, "opentelemetry.ext.redis": DatadogSpanTypes.REDIS, diff --git a/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/propagator.py b/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/propagator.py index d6595e8d93..6ff192f425 100644 --- a/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/propagator.py +++ b/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/propagator.py @@ -16,10 +16,7 @@ from opentelemetry import trace from opentelemetry.context import Context -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, -) +from opentelemetry.trace import get_current_span, set_span_in_context from opentelemetry.trace.propagation.httptextformat import ( Getter, HTTPTextFormat, @@ -88,7 +85,7 @@ def inject( carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: - span = get_span_from_context(context=context) + span = get_current_span(context) sampled = (trace.TraceFlags.SAMPLED & span.context.trace_flags) != 0 set_in_carrier( carrier, self.TRACE_ID_KEY, format_trace_id(span.context.trace_id), diff --git a/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/version.py b/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/version.py +++ b/ext/opentelemetry-ext-datadog/src/opentelemetry/ext/datadog/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-datadog/tests/test_datadog_format.py b/ext/opentelemetry-ext-datadog/tests/test_datadog_format.py index cf2fbf4220..31633f8370 100644 --- a/ext/opentelemetry-ext-datadog/tests/test_datadog_format.py +++ b/ext/opentelemetry-ext-datadog/tests/test_datadog_format.py @@ -17,10 +17,7 @@ from opentelemetry import trace as trace_api from opentelemetry.ext.datadog import constants, propagator from opentelemetry.sdk import trace -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, -) +from opentelemetry.trace import get_current_span, set_span_in_context FORMAT = propagator.DatadogFormat() @@ -45,7 +42,7 @@ def test_malformed_headers(self): """Test with no Datadog headers""" malformed_trace_id_key = FORMAT.TRACE_ID_KEY + "-x" malformed_parent_id_key = FORMAT.PARENT_ID_KEY + "-x" - context = get_span_from_context( + context = get_current_span( FORMAT.extract( get_as_list, { @@ -66,7 +63,7 @@ def test_missing_trace_id(self): } ctx = FORMAT.extract(get_as_list, carrier) - span_context = get_span_from_context(ctx).get_context() + span_context = get_current_span(ctx).get_context() self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) def test_missing_parent_id(self): @@ -76,12 +73,12 @@ def test_missing_parent_id(self): } ctx = FORMAT.extract(get_as_list, carrier) - span_context = get_span_from_context(ctx).get_context() + span_context = get_current_span(ctx).get_context() self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) def test_context_propagation(self): """Test the propagation of Datadog headers.""" - parent_context = get_span_from_context( + parent_context = get_current_span( FORMAT.extract( get_as_list, { @@ -138,7 +135,7 @@ def test_context_propagation(self): def test_sampling_priority_auto_reject(self): """Test sampling priority rejected.""" - parent_context = get_span_from_context( + parent_context = get_current_span( FORMAT.extract( get_as_list, { diff --git a/ext/opentelemetry-ext-dbapi/setup.cfg b/ext/opentelemetry-ext-dbapi/setup.cfg index e352201bb6..4ea5913e17 100644 --- a/ext/opentelemetry-ext-dbapi/setup.cfg +++ b/ext/opentelemetry-ext-dbapi/setup.cfg @@ -40,12 +40,13 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] test = - opentelemetry-test == 0.9.dev0 + opentelemetry-test == 0.10.dev0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-dbapi/src/opentelemetry/ext/dbapi/__init__.py b/ext/opentelemetry-ext-dbapi/src/opentelemetry/ext/dbapi/__init__.py index a5fda62dea..6b81bedf03 100644 --- a/ext/opentelemetry-ext-dbapi/src/opentelemetry/ext/dbapi/__init__.py +++ b/ext/opentelemetry-ext-dbapi/src/opentelemetry/ext/dbapi/__init__.py @@ -47,6 +47,7 @@ import wrapt from opentelemetry.ext.dbapi.version import __version__ +from opentelemetry.instrumentation.utils import unwrap from opentelemetry.trace import SpanKind, Tracer, TracerProvider, get_tracer from opentelemetry.trace.status import Status, StatusCanonicalCode @@ -141,9 +142,7 @@ def unwrap_connect( connect_module: Module name where the connect method is available. connect_method_name: The connect method name. """ - conn = getattr(connect_module, connect_method_name, None) - if isinstance(conn, wrapt.ObjectProxy): - setattr(connect_module, connect_method_name, conn.__wrapped__) + unwrap(connect_module, connect_method_name) def instrument_connection( diff --git a/ext/opentelemetry-ext-dbapi/src/opentelemetry/ext/dbapi/version.py b/ext/opentelemetry-ext-dbapi/src/opentelemetry/ext/dbapi/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-dbapi/src/opentelemetry/ext/dbapi/version.py +++ b/ext/opentelemetry-ext-dbapi/src/opentelemetry/ext/dbapi/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-django/setup.cfg b/ext/opentelemetry-ext-django/setup.cfg index bf72f9d981..8ed134ff19 100644 --- a/ext/opentelemetry-ext-django/setup.cfg +++ b/ext/opentelemetry-ext-django/setup.cfg @@ -41,13 +41,13 @@ package_dir= packages=find_namespace: install_requires = django >= 1.10 - opentelemetry-ext-wsgi == 0.9.dev0 - opentelemetry-auto-instrumentation == 0.9.dev0 - opentelemetry-api == 0.9.dev0 + opentelemetry-ext-wsgi == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 + opentelemetry-api == 0.10.dev0 [options.extras_require] test = - opentelemetry-test == 0.9.dev0 + opentelemetry-test == 0.10.dev0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-django/src/opentelemetry/ext/django/__init__.py b/ext/opentelemetry-ext-django/src/opentelemetry/ext/django/__init__.py index d4e78351aa..3eb3b2dd72 100644 --- a/ext/opentelemetry-ext-django/src/opentelemetry/ext/django/__init__.py +++ b/ext/opentelemetry-ext-django/src/opentelemetry/ext/django/__init__.py @@ -16,9 +16,9 @@ from django.conf import settings -from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor from opentelemetry.configuration import Configuration from opentelemetry.ext.django.middleware import _DjangoMiddleware +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor _logger = getLogger(__name__) diff --git a/ext/opentelemetry-ext-django/src/opentelemetry/ext/django/version.py b/ext/opentelemetry-ext-django/src/opentelemetry/ext/django/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-django/src/opentelemetry/ext/django/version.py +++ b/ext/opentelemetry-ext-django/src/opentelemetry/ext/django/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-flask/setup.cfg b/ext/opentelemetry-ext-flask/setup.cfg index 3258717419..4c2602a7b2 100644 --- a/ext/opentelemetry-ext-flask/setup.cfg +++ b/ext/opentelemetry-ext-flask/setup.cfg @@ -41,14 +41,14 @@ package_dir= packages=find_namespace: install_requires = flask ~= 1.0 - opentelemetry-ext-wsgi == 0.9.dev0 - opentelemetry-auto-instrumentation == 0.9.dev0 - opentelemetry-api == 0.9.dev0 + opentelemetry-ext-wsgi == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 + opentelemetry-api == 0.10.dev0 [options.extras_require] test = flask~=1.0 - opentelemetry-test == 0.9.dev0 + opentelemetry-test == 0.10.dev0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py index cd94dc7e47..20d6501a93 100644 --- a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py +++ b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py @@ -53,8 +53,8 @@ def hello(): import opentelemetry.ext.wsgi as otel_wsgi from opentelemetry import configuration, context, propagators, trace -from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor from opentelemetry.ext.flask.version import __version__ +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.util import disable_trace, time_ns _logger = getLogger(__name__) diff --git a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/version.py b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/version.py +++ b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-grpc/setup.cfg b/ext/opentelemetry-ext-grpc/setup.cfg index b2c7cc3203..1748bfb7b7 100644 --- a/ext/opentelemetry-ext-grpc/setup.cfg +++ b/ext/opentelemetry-ext-grpc/setup.cfg @@ -40,13 +40,13 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9.dev0 + opentelemetry-api == 0.10.dev0 grpcio ~= 1.27 [options.extras_require] test = - opentelemetry-test == 0.9.dev0 - opentelemetry-sdk == 0.9.dev0 + opentelemetry-test == 0.10.dev0 + opentelemetry-sdk == 0.10.dev0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-grpc/src/opentelemetry/ext/grpc/version.py b/ext/opentelemetry-ext-grpc/src/opentelemetry/ext/grpc/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-grpc/src/opentelemetry/ext/grpc/version.py +++ b/ext/opentelemetry-ext-grpc/src/opentelemetry/ext/grpc/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-grpc/tests/test_server_interceptor.py b/ext/opentelemetry-ext-grpc/tests/test_server_interceptor.py index ef42b2299f..e10d9c838c 100644 --- a/ext/opentelemetry-ext-grpc/tests/test_server_interceptor.py +++ b/ext/opentelemetry-ext-grpc/tests/test_server_interceptor.py @@ -138,15 +138,13 @@ def test_span_lifetime(self): """Check that the span is active for the duration of the call.""" interceptor = server_interceptor() - tracer = self.tracer_provider.get_tracer(__name__) # To capture the current span at the time the handler is called active_span_in_handler = None def handler(request, context): nonlocal active_span_in_handler - # The current span is shared among all the tracers. - active_span_in_handler = tracer.get_current_span() + active_span_in_handler = trace.get_current_span() return b"" server = grpc.server( @@ -159,13 +157,13 @@ def handler(request, context): port = server.add_insecure_port("[::]:0") channel = grpc.insecure_channel("localhost:{:d}".format(port)) - active_span_before_call = tracer.get_current_span() + active_span_before_call = trace.get_current_span() try: server.start() channel.unary_unary("")(b"") finally: server.stop(None) - active_span_after_call = tracer.get_current_span() + active_span_after_call = trace.get_current_span() self.assertIsNone(active_span_before_call) self.assertIsNone(active_span_after_call) @@ -175,15 +173,13 @@ def handler(request, context): def test_sequential_server_spans(self): """Check that sequential RPCs get separate server spans.""" - tracer = self.tracer_provider.get_tracer(__name__) - interceptor = server_interceptor() # Capture the currently active span in each thread active_spans_in_handler = [] def handler(request, context): - active_spans_in_handler.append(tracer.get_current_span()) + active_spans_in_handler.append(trace.get_current_span()) return b"" server = grpc.server( @@ -222,8 +218,6 @@ def test_concurrent_server_spans(self): context. """ - tracer = self.tracer_provider.get_tracer(__name__) - interceptor = server_interceptor() # Capture the currently active span in each thread @@ -232,7 +226,7 @@ def test_concurrent_server_spans(self): def handler(request, context): latch() - active_spans_in_handler.append(tracer.get_current_span()) + active_spans_in_handler.append(trace.get_current_span()) return b"" server = grpc.server( diff --git a/ext/opentelemetry-ext-jaeger/setup.cfg b/ext/opentelemetry-ext-jaeger/setup.cfg index 339ac32c28..e213e4558e 100644 --- a/ext/opentelemetry-ext-jaeger/setup.cfg +++ b/ext/opentelemetry-ext-jaeger/setup.cfg @@ -41,8 +41,11 @@ package_dir= packages=find_namespace: install_requires = thrift >= 0.10.0 - opentelemetry-api == 0.9.dev0 - opentelemetry-sdk == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-sdk == 0.10.dev0 [options.packages.find] where = src + +[options.extras_require] +test = diff --git a/ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/version.py b/ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/version.py index 3f34fbd58e..d21fcb8168 100644 --- a/ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/version.py +++ b/ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-jinja2/setup.cfg b/ext/opentelemetry-ext-jinja2/setup.cfg index a9428aec60..41d6ff2fb8 100644 --- a/ext/opentelemetry-ext-jinja2/setup.cfg +++ b/ext/opentelemetry-ext-jinja2/setup.cfg @@ -39,14 +39,14 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9.dev0 - opentelemetry-auto-instrumentation == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 jinja2~=2.7 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] test = - opentelemetry-test == 0.9.dev0 + opentelemetry-test == 0.10.dev0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py index 4aabb832ba..06be28873b 100644 --- a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py +++ b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py @@ -48,8 +48,9 @@ from wrapt import ObjectProxy from wrapt import wrap_function_wrapper as _wrap -from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor from opentelemetry.ext.jinja2.version import __version__ +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.instrumentation.utils import unwrap from opentelemetry.trace import SpanKind, get_tracer from opentelemetry.trace.status import Status, StatusCanonicalCode @@ -115,12 +116,6 @@ def _wrap_load_template(tracer, wrapped, _, args, kwargs): ) -def _unwrap(obj, attr): - func = getattr(obj, attr, None) - if func and isinstance(func, ObjectProxy) and hasattr(func, "__wrapped__"): - setattr(obj, attr, func.__wrapped__) - - class Jinja2Instrumentor(BaseInstrumentor): """An instrumentor for jinja2 @@ -141,7 +136,7 @@ def _instrument(self, **kwargs): ) def _uninstrument(self, **kwargs): - _unwrap(jinja2.Template, "render") - _unwrap(jinja2.Template, "generate") - _unwrap(jinja2.Environment, "compile") - _unwrap(jinja2.Environment, "_load_template") + unwrap(jinja2.Template, "render") + unwrap(jinja2.Template, "generate") + unwrap(jinja2.Environment, "compile") + unwrap(jinja2.Environment, "_load_template") diff --git a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/version.py b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/version.py +++ b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-mysql/setup.cfg b/ext/opentelemetry-ext-mysql/setup.cfg index afe53a651f..a00193b2f2 100644 --- a/ext/opentelemetry-ext-mysql/setup.cfg +++ b/ext/opentelemetry-ext-mysql/setup.cfg @@ -40,15 +40,15 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9.dev0 - opentelemetry-ext-dbapi == 0.9.dev0 - opentelemetry-auto-instrumentation == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-ext-dbapi == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 mysql-connector-python ~= 8.0 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] test = - opentelemetry-test == 0.9.dev0 + opentelemetry-test == 0.10.dev0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-mysql/src/opentelemetry/ext/mysql/__init__.py b/ext/opentelemetry-ext-mysql/src/opentelemetry/ext/mysql/__init__.py index a70c1d46ea..daf90c4bbf 100644 --- a/ext/opentelemetry-ext-mysql/src/opentelemetry/ext/mysql/__init__.py +++ b/ext/opentelemetry-ext-mysql/src/opentelemetry/ext/mysql/__init__.py @@ -46,9 +46,9 @@ import mysql.connector -from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor from opentelemetry.ext import dbapi from opentelemetry.ext.mysql.version import __version__ +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.trace import TracerProvider, get_tracer diff --git a/ext/opentelemetry-ext-mysql/src/opentelemetry/ext/mysql/version.py b/ext/opentelemetry-ext-mysql/src/opentelemetry/ext/mysql/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-mysql/src/opentelemetry/ext/mysql/version.py +++ b/ext/opentelemetry-ext-mysql/src/opentelemetry/ext/mysql/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-opencensusexporter/setup.cfg b/ext/opentelemetry-ext-opencensusexporter/setup.cfg index 11fc3a61c7..c85f3b3d00 100644 --- a/ext/opentelemetry-ext-opencensusexporter/setup.cfg +++ b/ext/opentelemetry-ext-opencensusexporter/setup.cfg @@ -42,9 +42,12 @@ packages=find_namespace: install_requires = grpcio >= 1.0.0, < 2.0.0 opencensus-proto >= 0.1.0, < 1.0.0 - opentelemetry-api == 0.9.dev0 - opentelemetry-sdk == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-sdk == 0.10.dev0 protobuf >= 3.8.0 [options.packages.find] where = src + +[options.extras_require] +test = diff --git a/ext/opentelemetry-ext-opencensusexporter/src/opentelemetry/ext/opencensusexporter/metrics_exporter/__init__.py b/ext/opentelemetry-ext-opencensusexporter/src/opentelemetry/ext/opencensusexporter/metrics_exporter/__init__.py index faa0788c7f..bb1a1ee888 100644 --- a/ext/opentelemetry-ext-opencensusexporter/src/opentelemetry/ext/opencensusexporter/metrics_exporter/__init__.py +++ b/ext/opentelemetry-ext-opencensusexporter/src/opentelemetry/ext/opencensusexporter/metrics_exporter/__init__.py @@ -114,10 +114,10 @@ def translate_to_collector( ) metric_descriptor = metrics_pb2.MetricDescriptor( - name=metric_record.metric.name, - description=metric_record.metric.description, - unit=metric_record.metric.unit, - type=get_collector_metric_type(metric_record.metric), + name=metric_record.instrument.name, + description=metric_record.instrument.description, + unit=metric_record.instrument.unit, + type=get_collector_metric_type(metric_record.instrument), label_keys=label_keys, ) @@ -151,14 +151,14 @@ def get_collector_point(metric_record: MetricRecord) -> metrics_pb2.Point: metric_record.aggregator.last_update_timestamp ) ) - if metric_record.metric.value_type == int: + if metric_record.instrument.value_type == int: point.int64_value = metric_record.aggregator.checkpoint - elif metric_record.metric.value_type == float: + elif metric_record.instrument.value_type == float: point.double_value = metric_record.aggregator.checkpoint else: raise TypeError( "Unsupported metric type: {}".format( - metric_record.metric.value_type + metric_record.instrument.value_type ) ) return point diff --git a/ext/opentelemetry-ext-opencensusexporter/src/opentelemetry/ext/opencensusexporter/version.py b/ext/opentelemetry-ext-opencensusexporter/src/opentelemetry/ext/opencensusexporter/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-opencensusexporter/src/opentelemetry/ext/opencensusexporter/version.py +++ b/ext/opentelemetry-ext-opencensusexporter/src/opentelemetry/ext/opencensusexporter/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-opencensusexporter/tests/test_otcollector_metrics_exporter.py b/ext/opentelemetry-ext-opencensusexporter/tests/test_otcollector_metrics_exporter.py index 18b4a32806..f538e5acec 100644 --- a/ext/opentelemetry-ext-opencensusexporter/tests/test_otcollector_metrics_exporter.py +++ b/ext/opentelemetry-ext-opencensusexporter/tests/test_otcollector_metrics_exporter.py @@ -81,7 +81,7 @@ def test_get_collector_metric_type(self): self.assertIs(result, metrics_pb2.MetricDescriptor.UNSPECIFIED) def test_get_collector_point(self): - aggregator = aggregate.CounterAggregator() + aggregator = aggregate.SumAggregator() int_counter = self._meter.create_metric( "testName", "testDescription", "unit", int, Counter ) @@ -92,7 +92,7 @@ def test_get_collector_point(self): "testName", "testDescription", "unit", float, ValueRecorder ) result = metrics_exporter.get_collector_point( - MetricRecord(aggregator, self._key_labels, int_counter) + MetricRecord(int_counter, self._key_labels, aggregator) ) self.assertIsInstance(result, metrics_pb2.Point) self.assertIsInstance(result.timestamp, Timestamp) @@ -100,13 +100,13 @@ def test_get_collector_point(self): aggregator.update(123.5) aggregator.take_checkpoint() result = metrics_exporter.get_collector_point( - MetricRecord(aggregator, self._key_labels, float_counter) + MetricRecord(float_counter, self._key_labels, aggregator) ) self.assertEqual(result.double_value, 123.5) self.assertRaises( TypeError, metrics_exporter.get_collector_point( - MetricRecord(aggregator, self._key_labels, valuerecorder) + MetricRecord(valuerecorder, self._key_labels, aggregator) ), ) @@ -122,7 +122,7 @@ def test_export(self): "testname", "testdesc", "unit", int, Counter, ["environment"] ) record = MetricRecord( - aggregate.CounterAggregator(), self._key_labels, test_metric + test_metric, self._key_labels, aggregate.SumAggregator(), ) result = collector_exporter.export([record]) @@ -144,10 +144,10 @@ def test_translate_to_collector(self): test_metric = self._meter.create_metric( "testname", "testdesc", "unit", int, Counter, ["environment"] ) - aggregator = aggregate.CounterAggregator() + aggregator = aggregate.SumAggregator() aggregator.update(123) aggregator.take_checkpoint() - record = MetricRecord(aggregator, self._key_labels, test_metric) + record = MetricRecord(test_metric, self._key_labels, aggregator,) output_metrics = metrics_exporter.translate_to_collector([record]) self.assertEqual(len(output_metrics), 1) self.assertIsInstance(output_metrics[0], metrics_pb2.Metric) diff --git a/ext/opentelemetry-ext-opentracing-shim/setup.cfg b/ext/opentelemetry-ext-opentracing-shim/setup.cfg index 2ad18381c5..7f6b093ea0 100644 --- a/ext/opentelemetry-ext-opentracing-shim/setup.cfg +++ b/ext/opentelemetry-ext-opentracing-shim/setup.cfg @@ -42,11 +42,12 @@ packages=find_namespace: install_requires = Deprecated >= 1.2.6 opentracing ~= 2.0 - opentelemetry-api == 0.9.dev0 + opentelemetry-api == 0.10.dev0 [options.extras_require] test = - opentelemetry-test == 0.9.dev0 + opentelemetry-test == 0.10.dev0 + opentracing ~= 2.2.0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/__init__.py b/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/__init__.py index c62f063de2..81f25013ff 100644 --- a/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/__init__.py +++ b/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/__init__.py @@ -94,11 +94,7 @@ from opentelemetry import propagators from opentelemetry.ext.opentracing_shim import util from opentelemetry.ext.opentracing_shim.version import __version__ -from opentelemetry.trace import DefaultSpan -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, -) +from opentelemetry.trace import DefaultSpan, set_span_in_context logger = logging.getLogger(__name__) @@ -473,7 +469,7 @@ def active(self): shim and is likely to be handled in future versions. """ - span = self._tracer.unwrap().get_current_span() + span = trace_api.get_current_span() if span is None: return None @@ -703,6 +699,10 @@ def get_as_list(dict_object, key): propagator = propagators.get_global_httptextformat() ctx = propagator.extract(get_as_list, carrier) - otel_context = get_span_from_context(ctx).get_context() + span = trace_api.get_current_span(ctx) + if span is not None: + otel_context = span.get_context() + else: + otel_context = trace_api.INVALID_SPAN_CONTEXT return SpanContextShim(otel_context) diff --git a/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/version.py b/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/version.py +++ b/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py index abca2e052b..941d428069 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -24,7 +24,10 @@ from opentelemetry import propagators, trace from opentelemetry.ext.opentracing_shim import util from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.test.mock_httptextformat import MockHTTPTextFormat +from opentelemetry.test.mock_httptextformat import ( + MockHTTPTextFormat, + NOOPHTTPTextFormat, +) class TestShim(TestCase): @@ -515,6 +518,20 @@ def test_extract_http_headers(self): self.assertEqual(ctx.unwrap().trace_id, 1220) self.assertEqual(ctx.unwrap().span_id, 7478) + def test_extract_empty_context_returns_invalid_context(self): + """In the case where the propagator cannot extract a + SpanContext, extract should return and invalid span context. + """ + _old_propagator = propagators.get_global_httptextformat() + propagators.set_global_httptextformat(NOOPHTTPTextFormat()) + try: + carrier = {} + + ctx = self.shim.extract(opentracing.Format.HTTP_HEADERS, carrier) + self.assertEqual(ctx.unwrap(), trace.INVALID_SPAN_CONTEXT) + finally: + propagators.set_global_httptextformat(_old_propagator) + def test_extract_text_map(self): """Test `extract()` method for Format.TEXT_MAP.""" diff --git a/ext/opentelemetry-ext-otlp/CHANGELOG.md b/ext/opentelemetry-ext-otlp/CHANGELOG.md new file mode 100644 index 0000000000..896a782491 --- /dev/null +++ b/ext/opentelemetry-ext-otlp/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## Unreleased + +## 0.9b0 + +Released 2020-06-10 + +- Initial release diff --git a/ext/opentelemetry-ext-otlp/LICENSE b/ext/opentelemetry-ext-otlp/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/ext/opentelemetry-ext-otlp/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/ext/opentelemetry-ext-otlp/MANIFEST.in b/ext/opentelemetry-ext-otlp/MANIFEST.in new file mode 100644 index 0000000000..aed3e33273 --- /dev/null +++ b/ext/opentelemetry-ext-otlp/MANIFEST.in @@ -0,0 +1,9 @@ +graft src +graft tests +global-exclude *.pyc +global-exclude *.pyo +global-exclude __pycache__/* +include CHANGELOG.md +include MANIFEST.in +include README.rst +include LICENSE diff --git a/ext/opentelemetry-ext-otlp/README.rst b/ext/opentelemetry-ext-otlp/README.rst new file mode 100644 index 0000000000..ab233cbc62 --- /dev/null +++ b/ext/opentelemetry-ext-otlp/README.rst @@ -0,0 +1,25 @@ +OpenTelemetry Collector Exporter +================================ + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/opentelemetry-ext-otlp.svg + :target: https://pypi.org/project/opentelemetry-ext-otlp/ + +This library allows to export data to the OpenTelemetry Collector using the OpenTelemetry Protocol. + +Installation +------------ + +:: + + pip install opentelemetry-ext-otlp + + +References +---------- + +* `OpenTelemetry Collector Exporter `_ +* `OpenTelemetry Collector `_ +* `OpenTelemetry `_ +* `OpenTelemetry Protocol Specification `_ diff --git a/ext/opentelemetry-ext-otlp/setup.cfg b/ext/opentelemetry-ext-otlp/setup.cfg new file mode 100644 index 0000000000..57fecc6fed --- /dev/null +++ b/ext/opentelemetry-ext-otlp/setup.cfg @@ -0,0 +1,54 @@ +# Copyright The OpenTelemetry Authors +# +# 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. +# +[metadata] +name = opentelemetry-ext-otlp +description = OpenTelemetry Collector Exporter +long_description = file: README.rst +long_description_content_type = text/x-rst +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +url = https://github.com/open-telemetry/opentelemetry-python/tree/master/ext/opentelemetry-ext-otlp +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + +[options] +python_requires = >=3.5 +package_dir= + =src +packages=find_namespace: +install_requires = + grpcio >= 1.0.0, < 2.0.0 + googleapis-common-protos ~= 1.52.0 + opentelemetry-api == 0.10.dev0 + opentelemetry-sdk == 0.10.dev0 + opentelemetry-proto == 0.10.dev0 + backoff ~= 1.10.0 + +[options.extras_require] +test = + pytest-grpc + +[options.packages.find] +where = src diff --git a/ext/opentelemetry-ext-otlp/setup.py b/ext/opentelemetry-ext-otlp/setup.py new file mode 100644 index 0000000000..64c30afbab --- /dev/null +++ b/ext/opentelemetry-ext-otlp/setup.py @@ -0,0 +1,26 @@ +# Copyright The OpenTelemetry Authors +# +# 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 setuptools + +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join( + BASE_DIR, "src", "opentelemetry", "ext", "otlp", "version.py" +) +PACKAGE_INFO = {} +with open(VERSION_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup(version=PACKAGE_INFO["__version__"]) diff --git a/ext/opentelemetry-ext-otlp/src/opentelemetry/ext/otlp/__init__.py b/ext/opentelemetry-ext-otlp/src/opentelemetry/ext/otlp/__init__.py new file mode 100644 index 0000000000..55c5dd69ef --- /dev/null +++ b/ext/opentelemetry-ext-otlp/src/opentelemetry/ext/otlp/__init__.py @@ -0,0 +1,50 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + + +""" +This library allows to export tracing data to an OTLP collector. + +Usage +----- + +The **OTLP Span Exporter** allows to export `OpenTelemetry`_ traces to the +`OTLP`_ collector. + + +.. _OTLP: https://github.com/open-telemetry/opentelemetry-collector/ +.. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/ + +.. code:: python + + from opentelemetry import trace + from opentelemetry.ext.otlp.trace_exporter import OTLPSpanExporter + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import BatchExportSpanProcessor + + trace.set_tracer_provider(TracerProvider()) + tracer = trace.get_tracer(__name__) + + otlp_exporter = OTLPSpanExporter(endpoint="localhost:55678") + + span_processor = BatchExportSpanProcessor(otlp_exporter) + + trace.get_tracer_provider().add_span_processor(span_processor) + + with tracer.start_as_current_span("foo"): + print("Hello world!") + +API +--- +""" diff --git a/ext/opentelemetry-ext-otlp/src/opentelemetry/ext/otlp/trace_exporter/__init__.py b/ext/opentelemetry-ext-otlp/src/opentelemetry/ext/otlp/trace_exporter/__init__.py new file mode 100644 index 0000000000..a25f785366 --- /dev/null +++ b/ext/opentelemetry-ext-otlp/src/opentelemetry/ext/otlp/trace_exporter/__init__.py @@ -0,0 +1,338 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +"""OTLP Span Exporter""" + +import logging +from time import sleep +from typing import Sequence + +from backoff import expo +from google.rpc.error_details_pb2 import RetryInfo +from grpc import ( + ChannelCredentials, + RpcError, + StatusCode, + insecure_channel, + secure_channel, +) + +from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ( + ExportTraceServiceRequest, +) +from opentelemetry.proto.collector.trace.v1.trace_service_pb2_grpc import ( + TraceServiceStub, +) +from opentelemetry.proto.common.v1.common_pb2 import AttributeKeyValue +from opentelemetry.proto.resource.v1.resource_pb2 import Resource +from opentelemetry.proto.trace.v1.trace_pb2 import ( + InstrumentationLibrarySpans, + ResourceSpans, +) +from opentelemetry.proto.trace.v1.trace_pb2 import Span as CollectorSpan +from opentelemetry.proto.trace.v1.trace_pb2 import Status +from opentelemetry.sdk.trace import Span as SDKSpan +from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult + +logger = logging.getLogger(__name__) + + +def _translate_key_values(key, value): + key_value = {"key": key} + + if isinstance(value, bool): + key_value["bool_value"] = value + + elif isinstance(value, str): + key_value["string_value"] = value + + elif isinstance(value, int): + key_value["int_value"] = value + + elif isinstance(value, float): + key_value["double_value"] = value + + else: + raise Exception( + "Invalid type {} of value {}".format(type(value), value) + ) + + return key_value + + +# pylint: disable=no-member +class OTLPSpanExporter(SpanExporter): + """OTLP span exporter + + Args: + endpoint: OpenTelemetry Collector receiver endpoint + credentials: Credentials object for server authentication + metadata: Metadata to send when exporting + """ + + def __init__( + self, + endpoint="localhost:55678", + credentials: ChannelCredentials = None, + metadata=None, + ): + super().__init__() + + self._metadata = metadata + self._collector_span_kwargs = None + + if credentials is None: + self._client = TraceServiceStub(insecure_channel(endpoint)) + else: + self._client = TraceServiceStub( + secure_channel(endpoint, credentials) + ) + + def _translate_name(self, sdk_span): + self._collector_span_kwargs["name"] = sdk_span.name + + def _translate_start_time(self, sdk_span): + self._collector_span_kwargs[ + "start_time_unix_nano" + ] = sdk_span.start_time + + def _translate_end_time(self, sdk_span): + self._collector_span_kwargs["end_time_unix_nano"] = sdk_span.end_time + + def _translate_span_id(self, sdk_span): + self._collector_span_kwargs[ + "span_id" + ] = sdk_span.context.span_id.to_bytes(8, "big") + + def _translate_trace_id(self, sdk_span): + self._collector_span_kwargs[ + "trace_id" + ] = sdk_span.context.trace_id.to_bytes(16, "big") + + def _translate_parent(self, sdk_span): + if sdk_span.parent is not None: + self._collector_span_kwargs[ + "parent_span_id" + ] = sdk_span.parent.span_id.to_bytes(8, "big") + + def _translate_context_trace_state(self, sdk_span): + if sdk_span.context.trace_state is not None: + self._collector_span_kwargs["trace_state"] = ",".join( + [ + "{}={}".format(key, value) + for key, value in (sdk_span.context.trace_state.items()) + ] + ) + + def _translate_attributes(self, sdk_span): + if sdk_span.attributes: + + self._collector_span_kwargs["attributes"] = [] + + for key, value in sdk_span.attributes.items(): + + try: + self._collector_span_kwargs["attributes"].append( + AttributeKeyValue(**_translate_key_values(key, value)) + ) + except Exception as error: # pylint: disable=broad-except + logger.exception(error) + + def _translate_events(self, sdk_span): + if sdk_span.events: + self._collector_span_kwargs["events"] = [] + + for sdk_span_event in sdk_span.events: + + collector_span_event = CollectorSpan.Event( + name=sdk_span_event.name, + time_unix_nano=sdk_span_event.timestamp, + ) + + for key, value in sdk_span_event.attributes.items(): + try: + collector_span_event.attributes.append( + AttributeKeyValue( + **_translate_key_values(key, value) + ) + ) + # pylint: disable=broad-except + except Exception as error: + logger.exception(error) + + self._collector_span_kwargs["events"].append( + collector_span_event + ) + + def _translate_links(self, sdk_span): + if sdk_span.links: + self._collector_span_kwargs["links"] = [] + + for sdk_span_link in sdk_span.links: + + collector_span_link = CollectorSpan.Link( + trace_id=( + sdk_span_link.context.trace_id.to_bytes(16, "big") + ), + span_id=(sdk_span_link.context.span_id.to_bytes(8, "big")), + ) + + for key, value in sdk_span_link.attributes.items(): + try: + collector_span_link.attributes.append( + AttributeKeyValue( + **_translate_key_values(key, value) + ) + ) + # pylint: disable=broad-except + except Exception as error: + logger.exception(error) + + self._collector_span_kwargs["links"].append( + collector_span_link + ) + + def _translate_status(self, sdk_span): + if sdk_span.status is not None: + self._collector_span_kwargs["status"] = Status( + code=sdk_span.status.canonical_code.value, + message=sdk_span.status.description, + ) + + def _translate_spans( + self, sdk_spans: Sequence[SDKSpan], + ) -> ExportTraceServiceRequest: + + sdk_resource_instrumentation_library_spans = {} + + for sdk_span in sdk_spans: + + if sdk_span.resource not in ( + sdk_resource_instrumentation_library_spans.keys() + ): + sdk_resource_instrumentation_library_spans[ + sdk_span.resource + ] = InstrumentationLibrarySpans() + + self._collector_span_kwargs = {} + + self._translate_name(sdk_span) + self._translate_start_time(sdk_span) + self._translate_end_time(sdk_span) + self._translate_span_id(sdk_span) + self._translate_trace_id(sdk_span) + self._translate_parent(sdk_span) + self._translate_context_trace_state(sdk_span) + self._translate_attributes(sdk_span) + self._translate_events(sdk_span) + self._translate_links(sdk_span) + self._translate_status(sdk_span) + + self._collector_span_kwargs["kind"] = getattr( + CollectorSpan.SpanKind, sdk_span.kind.name + ) + + sdk_resource_instrumentation_library_spans[ + sdk_span.resource + ].spans.append(CollectorSpan(**self._collector_span_kwargs)) + + resource_spans = [] + + for ( + sdk_resource, + instrumentation_library_spans, + ) in sdk_resource_instrumentation_library_spans.items(): + + collector_resource = Resource() + + for key, value in sdk_resource.labels.items(): + + try: + collector_resource.attributes.append( + AttributeKeyValue(**_translate_key_values(key, value)) + ) + except Exception as error: # pylint: disable=broad-except + logger.exception(error) + + resource_spans.append( + ResourceSpans( + resource=collector_resource, + instrumentation_library_spans=[ + instrumentation_library_spans + ], + ) + ) + + return ExportTraceServiceRequest(resource_spans=resource_spans) + + def export(self, spans: Sequence[SDKSpan]) -> SpanExportResult: + # expo returns a generator that yields delay values which grow + # exponentially. Once delay is greater than max_value, the yielded + # value will remain constant. + # max_value is set to 900 (900 seconds is 15 minutes) to use the same + # value as used in the Go implementation. + + max_value = 900 + + for delay in expo(max_value=max_value): + + if delay == max_value: + return SpanExportResult.FAILURE + + try: + self._client.Export( + request=self._translate_spans(spans), + metadata=self._metadata, + ) + + return SpanExportResult.SUCCESS + + except RpcError as error: + + if error.code() in [ + StatusCode.CANCELLED, + StatusCode.DEADLINE_EXCEEDED, + StatusCode.PERMISSION_DENIED, + StatusCode.UNAUTHENTICATED, + StatusCode.RESOURCE_EXHAUSTED, + StatusCode.ABORTED, + StatusCode.OUT_OF_RANGE, + StatusCode.UNAVAILABLE, + StatusCode.DATA_LOSS, + ]: + + retry_info_bin = dict(error.trailing_metadata()).get( + "google.rpc.retryinfo-bin" + ) + if retry_info_bin is not None: + retry_info = RetryInfo() + retry_info.ParseFromString(retry_info_bin) + delay = ( + retry_info.retry_delay.seconds + + retry_info.retry_delay.nanos / 1.0e9 + ) + + logger.debug("Waiting %ss before retrying export of span") + sleep(delay) + continue + + if error.code() == StatusCode.OK: + return SpanExportResult.SUCCESS + + return SpanExportResult.FAILURE + + return SpanExportResult.FAILURE + + def shutdown(self): + pass diff --git a/ext/opentelemetry-ext-otlp/src/opentelemetry/ext/otlp/version.py b/ext/opentelemetry-ext-otlp/src/opentelemetry/ext/otlp/version.py new file mode 100644 index 0000000000..6d4fefa599 --- /dev/null +++ b/ext/opentelemetry-ext-otlp/src/opentelemetry/ext/otlp/version.py @@ -0,0 +1,15 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-otlp/tests/__init__.py b/ext/opentelemetry-ext-otlp/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ext/opentelemetry-ext-otlp/tests/test_otlp_trace_exporter.py b/ext/opentelemetry-ext-otlp/tests/test_otlp_trace_exporter.py new file mode 100644 index 0000000000..db42f8ff6d --- /dev/null +++ b/ext/opentelemetry-ext-otlp/tests/test_otlp_trace_exporter.py @@ -0,0 +1,261 @@ +# Copyright The OpenTelemetry Authors +# +# 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 OrderedDict +from concurrent.futures import ThreadPoolExecutor +from unittest import TestCase +from unittest.mock import Mock, PropertyMock, patch + +from google.protobuf.duration_pb2 import Duration +from google.rpc.error_details_pb2 import RetryInfo +from grpc import StatusCode, server + +from opentelemetry.ext.otlp.trace_exporter import OTLPSpanExporter +from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ( + ExportTraceServiceRequest, + ExportTraceServiceResponse, +) +from opentelemetry.proto.collector.trace.v1.trace_service_pb2_grpc import ( + TraceServiceServicer, + add_TraceServiceServicer_to_server, +) +from opentelemetry.proto.common.v1.common_pb2 import AttributeKeyValue +from opentelemetry.proto.resource.v1.resource_pb2 import ( + Resource as CollectorResource, +) +from opentelemetry.proto.trace.v1.trace_pb2 import ( + InstrumentationLibrarySpans, + ResourceSpans, +) +from opentelemetry.proto.trace.v1.trace_pb2 import Span as CollectorSpan +from opentelemetry.proto.trace.v1.trace_pb2 import Status +from opentelemetry.sdk.resources import Resource as SDKResource +from opentelemetry.sdk.trace import Span, TracerProvider +from opentelemetry.sdk.trace.export import ( + SimpleExportSpanProcessor, + SpanExportResult, +) +from opentelemetry.trace import SpanKind + + +class TraceServiceServicerUNAVAILABLEDelay(TraceServiceServicer): + # pylint: disable=invalid-name,unused-argument,no-self-use + def Export(self, request, context): + context.set_code(StatusCode.UNAVAILABLE) + + context.send_initial_metadata( + (("google.rpc.retryinfo-bin", RetryInfo().SerializeToString()),) + ) + context.set_trailing_metadata( + ( + ( + "google.rpc.retryinfo-bin", + RetryInfo( + retry_delay=Duration(seconds=4) + ).SerializeToString(), + ), + ) + ) + + return ExportTraceServiceResponse() + + +class TraceServiceServicerUNAVAILABLE(TraceServiceServicer): + # pylint: disable=invalid-name,unused-argument,no-self-use + def Export(self, request, context): + context.set_code(StatusCode.UNAVAILABLE) + + return ExportTraceServiceResponse() + + +class TraceServiceServicerSUCCESS(TraceServiceServicer): + # pylint: disable=invalid-name,unused-argument,no-self-use + def Export(self, request, context): + context.set_code(StatusCode.OK) + + return ExportTraceServiceResponse() + + +class TestOTLPSpanExporter(TestCase): + def setUp(self): + tracer_provider = TracerProvider() + self.exporter = OTLPSpanExporter() + tracer_provider.add_span_processor( + SimpleExportSpanProcessor(self.exporter) + ) + self.tracer = tracer_provider.get_tracer(__name__) + + self.server = server(ThreadPoolExecutor(max_workers=10)) + + self.server.add_insecure_port("[::]:55678") + + self.server.start() + + event_mock = Mock( + **{ + "timestamp": 1591240820506462784, + "attributes": OrderedDict([("a", 1), ("b", False)]), + } + ) + + type(event_mock).name = PropertyMock(return_value="a") + + self.span = Span( + "a", + context=Mock( + **{ + "trace_state": OrderedDict([("a", "b"), ("c", "d")]), + "span_id": 10217189687419569865, + "trace_id": 67545097771067222548457157018666467027, + } + ), + resource=SDKResource(OrderedDict([("a", 1), ("b", False)])), + parent=Mock(**{"span_id": 12345}), + attributes=OrderedDict([("a", 1), ("b", True)]), + events=[event_mock], + links=[ + Mock( + **{ + "context.trace_id": 1, + "context.span_id": 2, + "attributes": OrderedDict([("a", 1), ("b", False)]), + "kind": SpanKind.INTERNAL, + } + ) + ], + ) + + self.span.start() + self.span.end() + + def tearDown(self): + self.server.stop(None) + + @patch("opentelemetry.ext.otlp.trace_exporter.expo") + @patch("opentelemetry.ext.otlp.trace_exporter.sleep") + def test_unavailable(self, mock_sleep, mock_expo): + + mock_expo.configure_mock(**{"return_value": [1]}) + + add_TraceServiceServicer_to_server( + TraceServiceServicerUNAVAILABLE(), self.server + ) + self.assertEqual( + self.exporter.export([self.span]), SpanExportResult.FAILURE + ) + mock_sleep.assert_called_with(1) + + @patch("opentelemetry.ext.otlp.trace_exporter.expo") + @patch("opentelemetry.ext.otlp.trace_exporter.sleep") + def test_unavailable_delay(self, mock_sleep, mock_expo): + + mock_expo.configure_mock(**{"return_value": [1]}) + + add_TraceServiceServicer_to_server( + TraceServiceServicerUNAVAILABLEDelay(), self.server + ) + self.assertEqual( + self.exporter.export([self.span]), SpanExportResult.FAILURE + ) + mock_sleep.assert_called_with(4) + + def test_success(self): + add_TraceServiceServicer_to_server( + TraceServiceServicerSUCCESS(), self.server + ) + self.assertEqual( + self.exporter.export([self.span]), SpanExportResult.SUCCESS + ) + + def test_translate_spans(self): + + expected = ExportTraceServiceRequest( + resource_spans=[ + ResourceSpans( + resource=CollectorResource( + attributes=[ + AttributeKeyValue(key="a", int_value=1), + AttributeKeyValue(key="b", bool_value=False), + ] + ), + instrumentation_library_spans=[ + InstrumentationLibrarySpans( + spans=[ + CollectorSpan( + # pylint: disable=no-member + name="a", + start_time_unix_nano=self.span.start_time, + end_time_unix_nano=self.span.end_time, + trace_state="a=b,c=d", + span_id=int.to_bytes( + 10217189687419569865, 8, "big" + ), + trace_id=int.to_bytes( + 67545097771067222548457157018666467027, + 16, + "big", + ), + parent_span_id=( + b"\000\000\000\000\000\00009" + ), + kind=CollectorSpan.SpanKind.INTERNAL, + attributes=[ + AttributeKeyValue( + key="a", int_value=1 + ), + AttributeKeyValue( + key="b", bool_value=True + ), + ], + events=[ + CollectorSpan.Event( + name="a", + time_unix_nano=1591240820506462784, + attributes=[ + AttributeKeyValue( + key="a", int_value=1 + ), + AttributeKeyValue( + key="b", int_value=False + ), + ], + ) + ], + status=Status(code=0, message=""), + links=[ + CollectorSpan.Link( + trace_id=int.to_bytes( + 1, 16, "big" + ), + span_id=int.to_bytes(2, 8, "big"), + attributes=[ + AttributeKeyValue( + key="a", int_value=1 + ), + AttributeKeyValue( + key="b", bool_value=False + ), + ], + ) + ], + ) + ] + ) + ], + ), + ] + ) + + # pylint: disable=protected-access + self.assertEqual(expected, self.exporter._translate_spans([self.span])) diff --git a/ext/opentelemetry-ext-prometheus/setup.cfg b/ext/opentelemetry-ext-prometheus/setup.cfg index 653c72a404..3e5d5cd957 100644 --- a/ext/opentelemetry-ext-prometheus/setup.cfg +++ b/ext/opentelemetry-ext-prometheus/setup.cfg @@ -41,8 +41,11 @@ package_dir= packages=find_namespace: install_requires = prometheus_client >= 0.5.0, < 1.0.0 - opentelemetry-api == 0.9.dev0 - opentelemetry-sdk == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-sdk == 0.10.dev0 [options.packages.find] where = src + +[options.extras_require] +test = diff --git a/ext/opentelemetry-ext-prometheus/src/opentelemetry/ext/prometheus/__init__.py b/ext/opentelemetry-ext-prometheus/src/opentelemetry/ext/prometheus/__init__.py index cc44621ac4..da22042dcc 100644 --- a/ext/opentelemetry-ext-prometheus/src/opentelemetry/ext/prometheus/__init__.py +++ b/ext/opentelemetry-ext-prometheus/src/opentelemetry/ext/prometheus/__init__.py @@ -29,7 +29,6 @@ from opentelemetry import metrics from opentelemetry.ext.prometheus import PrometheusMetricsExporter from opentelemetry.sdk.metrics import Counter, Meter - from opentelemetry.sdk.metrics.export.controller import PushController from prometheus_client import start_http_server # Start Prometheus client @@ -37,13 +36,12 @@ # Meter is responsible for creating and recording metrics metrics.set_meter_provider(MeterProvider()) - meter = metrics.meter() + meter = metrics.get_meter(__name__) # exporter to export metrics to Prometheus prefix = "MyAppPrefix" exporter = PrometheusMetricsExporter(prefix) - # controller collects metrics created from meter and exports it via the - # exporter every interval - controller = PushController(meter, exporter, 5) + # Starts the collect/export pipeline for metrics + metrics.get_meter_provider().start_pipeline(meter, exporter, 5) counter = meter.create_metric( "requests", @@ -152,22 +150,22 @@ def _translate_to_prometheus(self, metric_record: MetricRecord): metric_name = "" if self._prefix != "": metric_name = self._prefix + "_" - metric_name += self._sanitize(metric_record.metric.name) + metric_name += self._sanitize(metric_record.instrument.name) - if isinstance(metric_record.metric, Counter): + if isinstance(metric_record.instrument, Counter): prometheus_metric = CounterMetricFamily( name=metric_name, - documentation=metric_record.metric.description, + documentation=metric_record.instrument.description, labels=label_keys, ) prometheus_metric.add_metric( labels=label_values, value=metric_record.aggregator.checkpoint ) # TODO: Add support for histograms when supported in OT - elif isinstance(metric_record.metric, ValueRecorder): + elif isinstance(metric_record.instrument, ValueRecorder): prometheus_metric = UnknownMetricFamily( name=metric_name, - documentation=metric_record.metric.description, + documentation=metric_record.instrument.description, labels=label_keys, ) prometheus_metric.add_metric( @@ -176,7 +174,7 @@ def _translate_to_prometheus(self, metric_record: MetricRecord): else: logger.warning( - "Unsupported metric type. %s", type(metric_record.metric) + "Unsupported metric type. %s", type(metric_record.instrument) ) return prometheus_metric diff --git a/ext/opentelemetry-ext-prometheus/src/opentelemetry/ext/prometheus/version.py b/ext/opentelemetry-ext-prometheus/src/opentelemetry/ext/prometheus/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-prometheus/src/opentelemetry/ext/prometheus/version.py +++ b/ext/opentelemetry-ext-prometheus/src/opentelemetry/ext/prometheus/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py b/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py index f986e0c4f5..2ba6b70121 100644 --- a/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py +++ b/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py @@ -24,7 +24,7 @@ from opentelemetry.metrics import get_meter_provider, set_meter_provider from opentelemetry.sdk import metrics from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult -from opentelemetry.sdk.metrics.export.aggregate import CounterAggregator +from opentelemetry.sdk.metrics.export.aggregate import SumAggregator class TestPrometheusMetricExporter(unittest.TestCase): @@ -67,7 +67,7 @@ def test_shutdown(self): def test_export(self): with self._registry_register_patch: record = MetricRecord( - CounterAggregator(), self._labels_key, self._test_metric + self._test_metric, self._labels_key, SumAggregator(), ) exporter = PrometheusMetricsExporter() result = exporter.export([record]) @@ -87,10 +87,10 @@ def test_counter_to_prometheus(self): ) labels = {"environment@": "staging", "os": "Windows"} key_labels = metrics.get_labels_as_key(labels) - aggregator = CounterAggregator() + aggregator = SumAggregator() aggregator.update(123) aggregator.take_checkpoint() - record = MetricRecord(aggregator, key_labels, metric) + record = MetricRecord(metric, key_labels, aggregator) collector = CustomCollector("testprefix") collector.add_metrics_data([record]) @@ -118,7 +118,7 @@ def test_invalid_metric(self): ) labels = {"environment": "staging"} key_labels = metrics.get_labels_as_key(labels) - record = MetricRecord(None, key_labels, metric) + record = MetricRecord(metric, key_labels, None) collector = CustomCollector("testprefix") collector.add_metrics_data([record]) collector.collect() diff --git a/ext/opentelemetry-ext-psycopg2/setup.cfg b/ext/opentelemetry-ext-psycopg2/setup.cfg index 5de40eac26..252ff290d8 100644 --- a/ext/opentelemetry-ext-psycopg2/setup.cfg +++ b/ext/opentelemetry-ext-psycopg2/setup.cfg @@ -40,15 +40,15 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9.dev0 - opentelemetry-ext-dbapi == 0.9.dev0 - opentelemetry-auto-instrumentation == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-ext-dbapi == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 psycopg2-binary >= 2.7.3.1 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] test = - opentelemetry-test == 0.9.dev0 + opentelemetry-test == 0.10.dev0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-psycopg2/src/opentelemetry/ext/psycopg2/__init__.py b/ext/opentelemetry-ext-psycopg2/src/opentelemetry/ext/psycopg2/__init__.py index c82343f4de..871356ab2c 100644 --- a/ext/opentelemetry-ext-psycopg2/src/opentelemetry/ext/psycopg2/__init__.py +++ b/ext/opentelemetry-ext-psycopg2/src/opentelemetry/ext/psycopg2/__init__.py @@ -47,9 +47,9 @@ import psycopg2 import wrapt -from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor from opentelemetry.ext import dbapi from opentelemetry.ext.psycopg2.version import __version__ +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.trace import TracerProvider, get_tracer diff --git a/ext/opentelemetry-ext-psycopg2/src/opentelemetry/ext/psycopg2/version.py b/ext/opentelemetry-ext-psycopg2/src/opentelemetry/ext/psycopg2/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-psycopg2/src/opentelemetry/ext/psycopg2/version.py +++ b/ext/opentelemetry-ext-psycopg2/src/opentelemetry/ext/psycopg2/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-pymemcache/CHANGELOG.md b/ext/opentelemetry-ext-pymemcache/CHANGELOG.md new file mode 100644 index 0000000000..33144da913 --- /dev/null +++ b/ext/opentelemetry-ext-pymemcache/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## Unreleased + +- Initial release \ No newline at end of file diff --git a/ext/opentelemetry-ext-pymemcache/LICENSE b/ext/opentelemetry-ext-pymemcache/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/ext/opentelemetry-ext-pymemcache/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/ext/opentelemetry-ext-pymemcache/MANIFEST.IN b/ext/opentelemetry-ext-pymemcache/MANIFEST.IN new file mode 100644 index 0000000000..aed3e33273 --- /dev/null +++ b/ext/opentelemetry-ext-pymemcache/MANIFEST.IN @@ -0,0 +1,9 @@ +graft src +graft tests +global-exclude *.pyc +global-exclude *.pyo +global-exclude __pycache__/* +include CHANGELOG.md +include MANIFEST.in +include README.rst +include LICENSE diff --git a/ext/opentelemetry-ext-pymemcache/README.rst b/ext/opentelemetry-ext-pymemcache/README.rst new file mode 100644 index 0000000000..6328ff5f01 --- /dev/null +++ b/ext/opentelemetry-ext-pymemcache/README.rst @@ -0,0 +1,20 @@ +OpenTelemetry pymemcache Integration +==================================== + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/opentelemetry-ext-pymemcache.svg + :target: https://pypi.org/project/opentelemetry-ext-pymemcache/ + +Installation +------------ + +:: + + pip install opentelemetry-ext-pymemcache + + +References +---------- +* `OpenTelemetry Pymemcache Integration `_ +* `OpenTelemetry Project `_ diff --git a/ext/opentelemetry-ext-pymemcache/setup.cfg b/ext/opentelemetry-ext-pymemcache/setup.cfg new file mode 100644 index 0000000000..631d47579b --- /dev/null +++ b/ext/opentelemetry-ext-pymemcache/setup.cfg @@ -0,0 +1,57 @@ +# Copyright The OpenTelemetry Authors +# +# 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. +# +[metadata] +name = opentelemetry-ext-pymemcache +description = OpenTelemetry pymemcache integration +long_description = file: README.rst +long_description_content_type = text/x-rst +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +url = https://github.com/open-telemetry/opentelemetry-python/ext/opentelemetry-ext-pymemcache +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + +[options] +python_requires = >=3.4 +package_dir= + =src +packages=find_namespace: +install_requires = + opentelemetry-api == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 + pymemcache ~= 1.3 + wrapt >= 1.0.0, < 2.0.0 + +[options.extras_require] +test = + opentelemetry-test == 0.10.dev0 + +[options.packages.find] +where = src + +[options.entry_points] +opentelemetry_instrumentor = + pymemcache = opentelemetry.ext.pymemcache:PymemcacheInstrumentor diff --git a/ext/opentelemetry-ext-pymemcache/setup.py b/ext/opentelemetry-ext-pymemcache/setup.py new file mode 100644 index 0000000000..4b730b4f31 --- /dev/null +++ b/ext/opentelemetry-ext-pymemcache/setup.py @@ -0,0 +1,27 @@ +# Copyright The OpenTelemetry Authors +# +# 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 setuptools + +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join( + BASE_DIR, "src", "opentelemetry", "ext", "pymemcache", "version.py" +) +PACKAGE_INFO = {} +with open(VERSION_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup(version=PACKAGE_INFO["__version__"]) diff --git a/ext/opentelemetry-ext-pymemcache/src/opentelemetry/ext/pymemcache/__init__.py b/ext/opentelemetry-ext-pymemcache/src/opentelemetry/ext/pymemcache/__init__.py new file mode 100644 index 0000000000..e63bbe6f13 --- /dev/null +++ b/ext/opentelemetry-ext-pymemcache/src/opentelemetry/ext/pymemcache/__init__.py @@ -0,0 +1,197 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +""" + +Usage +----- + +The OpenTelemetry ``pymemcache`` integration traces pymemcache client operations + +Usage +----- + +.. code-block:: python + + from opentelemetry import trace + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.ext.pymemcache import PymemcacheInstrumentor + trace.set_tracer_provider(TracerProvider()) + PymemcacheInstrumentor().instrument() + from pymemcache.client.base import Client + client = Client(('localhost', 11211)) + client.set('some_key', 'some_value') + +API +--- +""" +# pylint: disable=no-value-for-parameter + +import logging + +import pymemcache +from wrapt import ObjectProxy +from wrapt import wrap_function_wrapper as _wrap + +from opentelemetry.ext.pymemcache.version import __version__ +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.instrumentation.utils import unwrap +from opentelemetry.trace import SpanKind, get_tracer + +logger = logging.getLogger(__name__) + +# Network attribute semantic convention here: +# https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/span-general.md#general-network-connection-attributes +_HOST = "net.peer.name" +_PORT = "net.peer.port" +# Database semantic conventions here: +# https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/database.md +_DB = "db.type" +_URL = "db.url" + +_DEFAULT_SERVICE = "memcached" +_RAWCMD = "db.statement" +_CMD = "memcached.command" +COMMANDS = [ + "set", + "set_many", + "add", + "replace", + "append", + "prepend", + "cas", + "get", + "get_many", + "gets", + "gets_many", + "delete", + "delete_many", + "incr", + "decr", + "touch", + "stats", + "version", + "flush_all", + "quit", + "set_multi", + "get_multi", +] + + +def _set_connection_attributes(span, instance): + for key, value in _get_address_attributes(instance).items(): + span.set_attribute(key, value) + + +def _with_tracer_wrapper(func): + """Helper for providing tracer for wrapper functions. + """ + + def _with_tracer(tracer, cmd): + def wrapper(wrapped, instance, args, kwargs): + # prevent double wrapping + if hasattr(wrapped, "__wrapped__"): + return wrapped(*args, **kwargs) + + return func(tracer, cmd, wrapped, instance, args, kwargs) + + return wrapper + + return _with_tracer + + +@_with_tracer_wrapper +def _wrap_cmd(tracer, cmd, wrapped, instance, args, kwargs): + with tracer.start_as_current_span( + _CMD, kind=SpanKind.INTERNAL, attributes={} + ) as span: + try: + if not args: + vals = "" + else: + vals = _get_query_string(args[0]) + + query = "{}{}{}".format(cmd, " " if vals else "", vals) + span.set_attribute(_RAWCMD, query) + + _set_connection_attributes(span, instance) + except Exception as ex: # pylint: disable=broad-except + logger.warning( + "Failed to set attributes for pymemcache span %s", str(ex) + ) + + return wrapped(*args, **kwargs) + + +def _get_query_string(arg): + + """Return the query values given the first argument to a pymemcache command. + + If there are multiple query values, they are joined together + space-separated. + """ + keys = "" + + if isinstance(arg, dict): + arg = list(arg) + + if isinstance(arg, str): + keys = arg + elif isinstance(arg, bytes): + keys = arg.decode() + elif isinstance(arg, list) and len(arg) >= 1: + if isinstance(arg[0], str): + keys = " ".join(arg) + elif isinstance(arg[0], bytes): + keys = b" ".join(arg).decode() + + return keys + + +def _get_address_attributes(instance): + """Attempt to get host and port from Client instance.""" + address_attributes = {} + address_attributes[_DB] = "memcached" + + # client.base.Client contains server attribute which is either a host/port tuple, or unix socket path string + # https://github.com/pinterest/pymemcache/blob/f02ddf73a28c09256589b8afbb3ee50f1171cac7/pymemcache/client/base.py#L228 + if hasattr(instance, "server"): + if isinstance(instance.server, tuple): + host, port = instance.server + address_attributes[_HOST] = host + address_attributes[_PORT] = port + address_attributes[_URL] = "memcached://{}:{}".format(host, port) + elif isinstance(instance.server, str): + address_attributes[_URL] = "memcached://{}".format(instance.server) + + return address_attributes + + +class PymemcacheInstrumentor(BaseInstrumentor): + """An instrumentor for pymemcache See `BaseInstrumentor`""" + + def _instrument(self, **kwargs): + tracer_provider = kwargs.get("tracer_provider") + tracer = get_tracer(__name__, __version__, tracer_provider) + + for cmd in COMMANDS: + _wrap( + "pymemcache.client.base", + "Client.{}".format(cmd), + _wrap_cmd(tracer, cmd), + ) + + def _uninstrument(self, **kwargs): + for command in COMMANDS: + unwrap(pymemcache.client.base.Client, "{}".format(command)) diff --git a/ext/opentelemetry-ext-pymemcache/src/opentelemetry/ext/pymemcache/version.py b/ext/opentelemetry-ext-pymemcache/src/opentelemetry/ext/pymemcache/version.py new file mode 100644 index 0000000000..6d4fefa599 --- /dev/null +++ b/ext/opentelemetry-ext-pymemcache/src/opentelemetry/ext/pymemcache/version.py @@ -0,0 +1,15 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-pymemcache/tests/__init__.py b/ext/opentelemetry-ext-pymemcache/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ext/opentelemetry-ext-pymemcache/tests/test_pymemcache.py b/ext/opentelemetry-ext-pymemcache/tests/test_pymemcache.py new file mode 100644 index 0000000000..aea4ad82f1 --- /dev/null +++ b/ext/opentelemetry-ext-pymemcache/tests/test_pymemcache.py @@ -0,0 +1,524 @@ +# Copyright The OpenTelemetry Authors +# +# 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 pymemcache +from pymemcache.exceptions import ( + MemcacheClientError, + MemcacheIllegalInputError, + MemcacheServerError, + MemcacheUnknownCommandError, + MemcacheUnknownError, +) + +from opentelemetry import trace as trace_api +from opentelemetry.ext.pymemcache import PymemcacheInstrumentor +from opentelemetry.test.test_base import TestBase +from opentelemetry.trace import get_tracer +from opentelemetry.trace.status import StatusCanonicalCode + +from .utils import MockSocket, _str + +TEST_HOST = "localhost" +TEST_PORT = 117711 + + +class PymemcacheClientTestCase( + TestBase +): # pylint: disable=too-many-public-methods + """ Tests for a patched pymemcache.client.base.Client. """ + + def setUp(self): + super().setUp() + PymemcacheInstrumentor().instrument() + + # pylint: disable=protected-access + self.tracer = get_tracer(__name__) + + def tearDown(self): + super().tearDown() + PymemcacheInstrumentor().uninstrument() + + def make_client(self, mock_socket_values, **kwargs): + # pylint: disable=attribute-defined-outside-init + self.client = pymemcache.client.base.Client( + (TEST_HOST, TEST_PORT), **kwargs + ) + self.client.sock = MockSocket(list(mock_socket_values)) + return self.client + + def check_spans(self, spans, num_expected, queries_expected): + """A helper for validating basic span information.""" + self.assertEqual(num_expected, len(spans)) + + for span, query in zip(spans, queries_expected): + self.assertEqual(span.name, "memcached.command") + self.assertIs(span.kind, trace_api.SpanKind.INTERNAL) + self.assertEqual( + span.attributes["net.peer.name"], "{}".format(TEST_HOST) + ) + self.assertEqual(span.attributes["net.peer.port"], TEST_PORT) + self.assertEqual(span.attributes["db.type"], "memcached") + self.assertEqual( + span.attributes["db.url"], + "memcached://{}:{}".format(TEST_HOST, TEST_PORT), + ) + self.assertEqual(span.attributes["db.statement"], query) + + def test_set_success(self): + client = self.make_client([b"STORED\r\n"]) + result = client.set(b"key", b"value", noreply=False) + self.assertTrue(result) + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["set key"]) + + def test_get_many_none_found(self): + client = self.make_client([b"END\r\n"]) + result = client.get_many([b"key1", b"key2"]) + assert result == {} + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["get_many key1 key2"]) + + def test_get_multi_none_found(self): + client = self.make_client([b"END\r\n"]) + # alias for get_many + result = client.get_multi([b"key1", b"key2"]) + assert result == {} + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["get_multi key1 key2"]) + + def test_set_multi_success(self): + client = self.make_client([b"STORED\r\n"]) + # Alias for set_many, a convienance function that calls set for every key + result = client.set_multi({b"key": b"value"}, noreply=False) + self.assertTrue(result) + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 2, ["set key", "set_multi key"]) + + def test_delete_not_found(self): + client = self.make_client([b"NOT_FOUND\r\n"]) + result = client.delete(b"key", noreply=False) + assert result is False + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["delete key"]) + + def test_incr_found(self): + client = self.make_client([b"STORED\r\n", b"1\r\n"]) + client.set(b"key", 0, noreply=False) + result = client.incr(b"key", 1, noreply=False) + assert result == 1 + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 2, ["set key", "incr key"]) + + def test_get_found(self): + client = self.make_client( + [b"STORED\r\n", b"VALUE key 0 5\r\nvalue\r\nEND\r\n"] + ) + result = client.set(b"key", b"value", noreply=False) + result = client.get(b"key") + assert result == b"value" + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 2, ["set key", "get key"]) + + def test_decr_found(self): + client = self.make_client([b"STORED\r\n", b"1\r\n"]) + client.set(b"key", 2, noreply=False) + result = client.decr(b"key", 1, noreply=False) + assert result == 1 + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 2, ["set key", "decr key"]) + + def test_add_stored(self): + client = self.make_client([b"STORED\r", b"\n"]) + result = client.add(b"key", b"value", noreply=False) + self.assertTrue(result) + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["add key"]) + + def test_delete_many_found(self): + client = self.make_client([b"STORED\r", b"\n", b"DELETED\r\n"]) + result = client.add(b"key", b"value", noreply=False) + # a convienance function that calls delete for every key + result = client.delete_many([b"key"], noreply=False) + self.assertTrue(result) + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans( + spans, 3, ["add key", "delete key", "delete_many key"] + ) + + def test_set_many_success(self): + client = self.make_client([b"STORED\r\n"]) + # a convienance function that calls set for every key + result = client.set_many({b"key": b"value"}, noreply=False) + self.assertTrue(result) + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 2, ["set key", "set_many key"]) + + def test_set_get(self): + client = self.make_client( + [b"STORED\r\n", b"VALUE key 0 5\r\nvalue\r\nEND\r\n"] + ) + client.set(b"key", b"value", noreply=False) + result = client.get(b"key") + assert _str(result) == "value" + + spans = self.memory_exporter.get_finished_spans() + + self.assertEqual(len(spans), 2) + self.assertEqual( + spans[0].attributes["db.url"], + "memcached://{}:{}".format(TEST_HOST, TEST_PORT), + ) + + def test_append_stored(self): + client = self.make_client([b"STORED\r\n"]) + result = client.append(b"key", b"value", noreply=False) + self.assertTrue(result) + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["append key"]) + + def test_prepend_stored(self): + client = self.make_client([b"STORED\r\n"]) + result = client.prepend(b"key", b"value", noreply=False) + self.assertTrue(result) + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["prepend key"]) + + def test_cas_stored(self): + client = self.make_client([b"STORED\r\n"]) + result = client.cas(b"key", b"value", b"cas", noreply=False) + self.assertTrue(result) + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["cas key"]) + + def test_cas_exists(self): + client = self.make_client([b"EXISTS\r\n"]) + result = client.cas(b"key", b"value", b"cas", noreply=False) + assert result is False + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["cas key"]) + + def test_cas_not_found(self): + client = self.make_client([b"NOT_FOUND\r\n"]) + result = client.cas(b"key", b"value", b"cas", noreply=False) + assert result is None + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["cas key"]) + + def test_delete_exception(self): + client = self.make_client([Exception("fail")]) + + def _delete(): + client.delete(b"key", noreply=False) + + with self.assertRaises(Exception): + _delete() + + spans = self.memory_exporter.get_finished_spans() + + span = spans[0] + + self.assertNotEqual(span.status.canonical_code, StatusCanonicalCode.OK) + + self.check_spans(spans, 1, ["delete key"]) + + def test_flush_all(self): + client = self.make_client([b"OK\r\n"]) + result = client.flush_all(noreply=False) + self.assertTrue(result) + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["flush_all"]) + + def test_incr_exception(self): + client = self.make_client([Exception("fail")]) + + def _incr(): + client.incr(b"key", 1) + + with self.assertRaises(Exception): + _incr() + + spans = self.memory_exporter.get_finished_spans() + + span = spans[0] + + self.assertNotEqual(span.status.canonical_code, StatusCanonicalCode.OK) + + self.check_spans(spans, 1, ["incr key"]) + + def test_get_error(self): + client = self.make_client([b"ERROR\r\n"]) + + def _get(): + client.get(b"key") + + with self.assertRaises(MemcacheUnknownCommandError): + _get() + + spans = self.memory_exporter.get_finished_spans() + + span = spans[0] + + self.assertNotEqual(span.status.canonical_code, StatusCanonicalCode.OK) + + self.check_spans(spans, 1, ["get key"]) + + def test_get_unknown_error(self): + client = self.make_client([b"foobarbaz\r\n"]) + + def _get(): + client.get(b"key") + + with self.assertRaises(MemcacheUnknownError): + _get() + + spans = self.memory_exporter.get_finished_spans() + + span = spans[0] + + self.assertNotEqual(span.status.canonical_code, StatusCanonicalCode.OK) + + self.check_spans(spans, 1, ["get key"]) + + def test_gets_found(self): + client = self.make_client([b"VALUE key 0 5 10\r\nvalue\r\nEND\r\n"]) + result = client.gets(b"key") + assert result == (b"value", b"10") + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["gets key"]) + + def test_touch_not_found(self): + client = self.make_client([b"NOT_FOUND\r\n"]) + result = client.touch(b"key", noreply=False) + assert result is False + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["touch key"]) + + def test_set_client_error(self): + client = self.make_client([b"CLIENT_ERROR some message\r\n"]) + + def _set(): + client.set("key", "value", noreply=False) + + with self.assertRaises(MemcacheClientError): + _set() + + spans = self.memory_exporter.get_finished_spans() + + span = spans[0] + + self.assertNotEqual(span.status.canonical_code, StatusCanonicalCode.OK) + + self.check_spans(spans, 1, ["set key"]) + + def test_set_server_error(self): + client = self.make_client([b"SERVER_ERROR some message\r\n"]) + + def _set(): + client.set(b"key", b"value", noreply=False) + + with self.assertRaises(MemcacheServerError): + _set() + + spans = self.memory_exporter.get_finished_spans() + + span = spans[0] + + self.assertNotEqual(span.status.canonical_code, StatusCanonicalCode.OK) + + self.check_spans(spans, 1, ["set key"]) + + def test_set_key_with_space(self): + client = self.make_client([b""]) + + def _set(): + client.set(b"key has space", b"value", noreply=False) + + with self.assertRaises(MemcacheIllegalInputError): + _set() + + spans = self.memory_exporter.get_finished_spans() + + span = spans[0] + + self.assertNotEqual(span.status.canonical_code, StatusCanonicalCode.OK) + + self.check_spans(spans, 1, ["set key has space"]) + + def test_quit(self): + client = self.make_client([]) + assert client.quit() is None + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["quit"]) + + def test_replace_not_stored(self): + client = self.make_client([b"NOT_STORED\r\n"]) + result = client.replace(b"key", b"value", noreply=False) + assert result is False + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["replace key"]) + + def test_version_success(self): + client = self.make_client( + [b"VERSION 1.2.3\r\n"], default_noreply=False + ) + result = client.version() + assert result == b"1.2.3" + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["version"]) + + def test_stats(self): + client = self.make_client([b"STAT fake_stats 1\r\n", b"END\r\n"]) + result = client.stats() + assert client.sock.send_bufs == [b"stats \r\n"] + assert result == {b"fake_stats": 1} + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 1, ["stats"]) + + def test_uninstrumented(self): + PymemcacheInstrumentor().uninstrument() + + client = self.make_client( + [b"STORED\r\n", b"VALUE key 0 5\r\nvalue\r\nEND\r\n"] + ) + client.set(b"key", b"value", noreply=False) + result = client.get(b"key") + assert _str(result) == "value" + + spans = self.memory_exporter.get_finished_spans() + + self.assertEqual(len(spans), 0) + + PymemcacheInstrumentor().instrument() + + +class PymemcacheHashClientTestCase(TestBase): + """ Tests for a patched pymemcache.client.hash.HashClient. """ + + def setUp(self): + super().setUp() + PymemcacheInstrumentor().instrument() + + # pylint: disable=protected-access + self.tracer = get_tracer(__name__) + + def tearDown(self): + super().tearDown() + PymemcacheInstrumentor().uninstrument() + + def make_client_pool( + self, hostname, mock_socket_values, serializer=None, **kwargs + ): # pylint: disable=no-self-use + mock_client = pymemcache.client.base.Client( + hostname, serializer=serializer, **kwargs + ) + mock_client.sock = MockSocket(mock_socket_values) + client = pymemcache.client.base.PooledClient( + hostname, serializer=serializer + ) + client.client_pool = pymemcache.pool.ObjectPool(lambda: mock_client) + return mock_client + + def make_client(self, *mock_socket_values, **kwargs): + current_port = TEST_PORT + + # pylint: disable=import-outside-toplevel + from pymemcache.client.hash import HashClient + + # pylint: disable=attribute-defined-outside-init + self.client = HashClient([], **kwargs) + ip = TEST_HOST + + for vals in mock_socket_values: + url_string = "{}:{}".format(ip, current_port) + clnt_pool = self.make_client_pool( + (ip, current_port), vals, **kwargs + ) + self.client.clients[url_string] = clnt_pool + self.client.hasher.add_node(url_string) + current_port += 1 + return self.client + + def check_spans(self, spans, num_expected, queries_expected): + """A helper for validating basic span information.""" + self.assertEqual(num_expected, len(spans)) + + for span, query in zip(spans, queries_expected): + self.assertEqual(span.name, "memcached.command") + self.assertIs(span.kind, trace_api.SpanKind.INTERNAL) + self.assertEqual( + span.attributes["net.peer.name"], "{}".format(TEST_HOST) + ) + self.assertEqual(span.attributes["net.peer.port"], TEST_PORT) + self.assertEqual(span.attributes["db.type"], "memcached") + self.assertEqual( + span.attributes["db.url"], + "memcached://{}:{}".format(TEST_HOST, TEST_PORT), + ) + self.assertEqual(span.attributes["db.statement"], query) + + def test_delete_many_found(self): + client = self.make_client([b"STORED\r", b"\n", b"DELETED\r\n"]) + result = client.add(b"key", b"value", noreply=False) + result = client.delete_many([b"key"], noreply=False) + self.assertTrue(result) + + spans = self.memory_exporter.get_finished_spans() + + self.check_spans(spans, 2, ["add key", "delete key"]) diff --git a/ext/opentelemetry-ext-pymemcache/tests/utils.py b/ext/opentelemetry-ext-pymemcache/tests/utils.py new file mode 100644 index 0000000000..361fb6e68c --- /dev/null +++ b/ext/opentelemetry-ext-pymemcache/tests/utils.py @@ -0,0 +1,74 @@ +# Copyright The OpenTelemetry Authors +# +# 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 collections +import socket + + +class MockSocket: + def __init__(self, recv_bufs, connect_failure=None): + self.recv_bufs = collections.deque(recv_bufs) + self.send_bufs = [] + self.closed = False + self.timeouts = [] + self.connect_failure = connect_failure + self.connections = [] + self.socket_options = [] + + def sendall(self, value): + self.send_bufs.append(value) + + def close(self): + self.closed = True + + def recv(self, size): # pylint: disable=unused-argument + value = self.recv_bufs.popleft() + if isinstance(value, Exception): + raise value + return value + + def settimeout(self, timeout): + self.timeouts.append(timeout) + + def connect(self, server): + if isinstance(self.connect_failure, Exception): + raise self.connect_failure + self.connections.append(server) + + def setsockopt(self, level, option, value): + self.socket_options.append((level, option, value)) + + +class MockSocketModule: + def __init__(self, connect_failure=None): + self.connect_failure = connect_failure + self.sockets = [] + + def socket(self): # noqa: A002 + soket = MockSocket([], connect_failure=self.connect_failure) + self.sockets.append(soket) + return soket + + def __getattr__(self, name): + return getattr(socket, name) + + +# Compatibility to get a string back from a request +def _str(string_input): + if isinstance(string_input, str): + return string_input + if isinstance(string_input, bytes): + return string_input.decode() + + return str(string_input) diff --git a/ext/opentelemetry-ext-pymongo/setup.cfg b/ext/opentelemetry-ext-pymongo/setup.cfg index ba1ea4026a..6cb5c8cc08 100644 --- a/ext/opentelemetry-ext-pymongo/setup.cfg +++ b/ext/opentelemetry-ext-pymongo/setup.cfg @@ -40,13 +40,13 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9.dev0 - opentelemetry-auto-instrumentation == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 pymongo ~= 3.1 [options.extras_require] test = - opentelemetry-test == 0.9.dev0 + opentelemetry-test == 0.10.dev0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-pymongo/src/opentelemetry/ext/pymongo/__init__.py b/ext/opentelemetry-ext-pymongo/src/opentelemetry/ext/pymongo/__init__.py index ebe52ed5f1..fa8a599539 100644 --- a/ext/opentelemetry-ext-pymongo/src/opentelemetry/ext/pymongo/__init__.py +++ b/ext/opentelemetry-ext-pymongo/src/opentelemetry/ext/pymongo/__init__.py @@ -43,8 +43,8 @@ from pymongo import monitoring from opentelemetry import trace -from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor from opentelemetry.ext.pymongo.version import __version__ +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.trace import SpanKind, get_tracer from opentelemetry.trace.status import Status, StatusCanonicalCode diff --git a/ext/opentelemetry-ext-pymongo/src/opentelemetry/ext/pymongo/version.py b/ext/opentelemetry-ext-pymongo/src/opentelemetry/ext/pymongo/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-pymongo/src/opentelemetry/ext/pymongo/version.py +++ b/ext/opentelemetry-ext-pymongo/src/opentelemetry/ext/pymongo/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-pymysql/setup.cfg b/ext/opentelemetry-ext-pymysql/setup.cfg index f08e4d3ee1..031a6562b3 100644 --- a/ext/opentelemetry-ext-pymysql/setup.cfg +++ b/ext/opentelemetry-ext-pymysql/setup.cfg @@ -40,14 +40,14 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9.dev0 - opentelemetry-ext-dbapi == 0.9.dev0 - opentelemetry-auto-instrumentation == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-ext-dbapi == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 PyMySQL ~= 0.9.3 [options.extras_require] test = - opentelemetry-test == 0.9.dev0 + opentelemetry-test == 0.10.dev0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-pymysql/src/opentelemetry/ext/pymysql/__init__.py b/ext/opentelemetry-ext-pymysql/src/opentelemetry/ext/pymysql/__init__.py index 14a320fdd0..b4d75543c6 100644 --- a/ext/opentelemetry-ext-pymysql/src/opentelemetry/ext/pymysql/__init__.py +++ b/ext/opentelemetry-ext-pymysql/src/opentelemetry/ext/pymysql/__init__.py @@ -47,9 +47,9 @@ import pymysql -from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor from opentelemetry.ext import dbapi from opentelemetry.ext.pymysql.version import __version__ +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.trace import TracerProvider, get_tracer diff --git a/ext/opentelemetry-ext-pymysql/src/opentelemetry/ext/pymysql/version.py b/ext/opentelemetry-ext-pymysql/src/opentelemetry/ext/pymysql/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-pymysql/src/opentelemetry/ext/pymysql/version.py +++ b/ext/opentelemetry-ext-pymysql/src/opentelemetry/ext/pymysql/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-pyramid/CHANGELOG.md b/ext/opentelemetry-ext-pyramid/CHANGELOG.md new file mode 100644 index 0000000000..efb1a08bbc --- /dev/null +++ b/ext/opentelemetry-ext-pyramid/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## Unreleased + +## 0.9b0 + +Released 2020-06-10 + +- Initial release \ No newline at end of file diff --git a/ext/opentelemetry-ext-pyramid/LICENSE b/ext/opentelemetry-ext-pyramid/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/ext/opentelemetry-ext-pyramid/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/ext/opentelemetry-ext-pyramid/MANIFEST.in b/ext/opentelemetry-ext-pyramid/MANIFEST.in new file mode 100644 index 0000000000..aed3e33273 --- /dev/null +++ b/ext/opentelemetry-ext-pyramid/MANIFEST.in @@ -0,0 +1,9 @@ +graft src +graft tests +global-exclude *.pyc +global-exclude *.pyo +global-exclude __pycache__/* +include CHANGELOG.md +include MANIFEST.in +include README.rst +include LICENSE diff --git a/ext/opentelemetry-ext-pyramid/README.rst b/ext/opentelemetry-ext-pyramid/README.rst new file mode 100644 index 0000000000..c7890ad095 --- /dev/null +++ b/ext/opentelemetry-ext-pyramid/README.rst @@ -0,0 +1,21 @@ +OpenTelemetry Pyramid Integration +================================= + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/opentelemetry-ext-pyramid.svg + :target: https://pypi.org/project/opentelemetry-ext-pyramid/ + +Installation +------------ + +:: + + pip install opentelemetry-ext-pyramid + + +References +---------- +* `OpenTelemetry Pyramid Integration `_ +* `OpenTelemetry Project `_ + diff --git a/ext/opentelemetry-ext-pyramid/setup.cfg b/ext/opentelemetry-ext-pyramid/setup.cfg new file mode 100644 index 0000000000..521f7c85ee --- /dev/null +++ b/ext/opentelemetry-ext-pyramid/setup.cfg @@ -0,0 +1,59 @@ +# Copyright The OpenTelemetry Authors +# +# 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. +# +[metadata] +name = opentelemetry-ext-pyramid +description = OpenTelemetry Pyramid integration +long_description = file: README.rst +long_description_content_type = text/x-rst +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +url = https://github.com/open-telemetry/opentelemetry-python/tree/master/ext/opentelemetry-ext-pyramid +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + +[options] +python_requires = >=3.4 +package_dir= + =src +packages=find_namespace: +install_requires = + pyramid >= 1.7 + opentelemetry-instrumentation == 0.10.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-ext-wsgi == 0.10.dev0 + wrapt >= 1.0.0, < 2.0.0 + +[options.extras_require] +test = + werkzeug == 0.16.1 + opentelemetry-test == 0.10.dev0 + +[options.packages.find] +where = src + +[options.entry_points] +opentelemetry_instrumentor = + pyramid = opentelemetry.ext.pyramid:PyramidInstrumentor diff --git a/ext/opentelemetry-ext-pyramid/setup.py b/ext/opentelemetry-ext-pyramid/setup.py new file mode 100644 index 0000000000..175db54ed5 --- /dev/null +++ b/ext/opentelemetry-ext-pyramid/setup.py @@ -0,0 +1,26 @@ +# Copyright The OpenTelemetry Authors +# +# 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 setuptools + +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join( + BASE_DIR, "src", "opentelemetry", "ext", "pyramid", "version.py" +) +PACKAGE_INFO = {} +with open(VERSION_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup(version=PACKAGE_INFO["__version__"]) diff --git a/ext/opentelemetry-ext-pyramid/src/opentelemetry/ext/pyramid/__init__.py b/ext/opentelemetry-ext-pyramid/src/opentelemetry/ext/pyramid/__init__.py new file mode 100644 index 0000000000..c3db25ee7c --- /dev/null +++ b/ext/opentelemetry-ext-pyramid/src/opentelemetry/ext/pyramid/__init__.py @@ -0,0 +1,148 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +""" +Pyramid instrumentation supporting `pyramid`_, it can be enabled by +using ``PyramidInstrumentor``. + +.. _pyramid: https://docs.pylonsproject.org/projects/pyramid/en/latest/ + +Usage +----- +There are two methods to instrument Pyramid: + +Method 1 (Instrument all Configurators): +---------------------------------------- + +.. code:: python + + from pyramid.config import Configurator + from opentelemetry.ext.pyramid import PyramidInstrumentor + + PyramidInstrumentor().instrument() + + config = Configurator() + + # use your config as normal + config.add_route('index', '/') + +Method 2 (Instrument one Configurator): +--------------------------------------- + +.. code:: python + + from pyramid.config import Configurator + from opentelemetry.ext.pyramid import PyramidInstrumentor + + config = Configurator() + PyramidInstrumentor().instrument_config(config) + + # use your config as normal + config.add_route('index', '/') + +Using ``pyramid.tweens`` setting: +--------------------------------- + +If you use Method 2 and then set tweens for your application with the ``pyramid.tweens`` setting, +you need to add ``opentelemetry.ext.pyramid.trace_tween_factory`` explicity to the list, +*as well as* instrumenting the config as shown above. + +For example: + +.. code:: python + + from pyramid.config import Configurator + from opentelemetry.ext.pyramid import PyramidInstrumentor + + settings = { + 'pyramid.tweens', 'opentelemetry.ext.pyramid.trace_tween_factory\\nyour_tween_no_1\\nyour_tween_no_2', + } + config = Configurator(settings=settings) + PyramidInstrumentor().instrument_config(config) + + # use your config as normal. + config.add_route('index', '/') + +API +--- +""" + +import typing + +from pyramid.config import Configurator +from pyramid.path import caller_package +from pyramid.settings import aslist +from wrapt import ObjectProxy +from wrapt import wrap_function_wrapper as _wrap + +from opentelemetry.ext.pyramid.callbacks import ( + SETTING_TRACE_ENABLED, + TWEEN_NAME, + trace_tween_factory, +) +from opentelemetry.ext.pyramid.version import __version__ +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.instrumentation.utils import unwrap +from opentelemetry.trace import TracerProvider, get_tracer + + +def _traced_init(wrapped, instance, args, kwargs): + settings = kwargs.get("settings", {}) + tweens = aslist(settings.get("pyramid.tweens", [])) + + if tweens and TWEEN_NAME not in settings: + # pyramid.tweens.EXCVIEW is the name of built-in exception view provided by + # pyramid. We need our tween to be before it, otherwise unhandled + # exceptions will be caught before they reach our tween. + tweens = [TWEEN_NAME] + tweens + + settings["pyramid.tweens"] = "\n".join(tweens) + + kwargs["settings"] = settings + + # `caller_package` works by walking a fixed amount of frames up the stack + # to find the calling package. So if we let the original `__init__` + # function call it, our wrapper will mess things up. + if not kwargs.get("package", None): + # Get the package for the third frame up from this one. + # Default is `level=2` which will give us the package from `wrapt` + # instead of the desired package (the caller) + kwargs["package"] = caller_package(level=3) + + wrapped(*args, **kwargs) + instance.include("opentelemetry.ext.pyramid.callbacks") + + +class PyramidInstrumentor(BaseInstrumentor): + def _instrument(self, **kwargs): + """Integrate with Pyramid Python library. + https://docs.pylonsproject.org/projects/pyramid/en/latest/ + """ + _wrap("pyramid.config", "Configurator.__init__", _traced_init) + + def _uninstrument(self, **kwargs): + """"Disable Pyramid instrumentation""" + unwrap(Configurator, "__init__") + + # pylint:disable=no-self-use + def instrument_config(self, config): + """Enable instrumentation in a Pyramid configurator. + + Args: + config: The Configurator to instrument. + """ + config.include("opentelemetry.ext.pyramid.callbacks") + + def uninstrument_config(self, config): + config.add_settings({SETTING_TRACE_ENABLED: False}) diff --git a/ext/opentelemetry-ext-pyramid/src/opentelemetry/ext/pyramid/callbacks.py b/ext/opentelemetry-ext-pyramid/src/opentelemetry/ext/pyramid/callbacks.py new file mode 100644 index 0000000000..e19e447bee --- /dev/null +++ b/ext/opentelemetry-ext-pyramid/src/opentelemetry/ext/pyramid/callbacks.py @@ -0,0 +1,172 @@ +from logging import getLogger + +from pyramid.events import BeforeTraversal +from pyramid.httpexceptions import HTTPException +from pyramid.settings import asbool +from pyramid.tweens import EXCVIEW + +import opentelemetry.ext.wsgi as otel_wsgi +from opentelemetry import configuration, context, propagators, trace +from opentelemetry.ext.pyramid.version import __version__ +from opentelemetry.util import disable_trace, time_ns + +TWEEN_NAME = "opentelemetry.ext.pyramid.trace_tween_factory" +SETTING_TRACE_ENABLED = "opentelemetry-pyramid.trace_enabled" + +_ENVIRON_STARTTIME_KEY = "opentelemetry-pyramid.starttime_key" +_ENVIRON_SPAN_KEY = "opentelemetry-pyramid.span_key" +_ENVIRON_ACTIVATION_KEY = "opentelemetry-pyramid.activation_key" +_ENVIRON_ENABLED_KEY = "opentelemetry-pyramid.tracing_enabled_key" +_ENVIRON_TOKEN = "opentelemetry-pyramid.token" + +_logger = getLogger(__name__) + + +def get_excluded_hosts(): + hosts = configuration.Configuration().PYRAMID_EXCLUDED_HOSTS or [] + if hosts: + hosts = str.split(hosts, ",") + return hosts + + +def get_excluded_paths(): + paths = configuration.Configuration().PYRAMID_EXCLUDED_PATHS or [] + if paths: + paths = str.split(paths, ",") + return paths + + +_excluded_hosts = get_excluded_hosts() +_excluded_paths = get_excluded_paths() + + +def includeme(config): + config.add_settings({SETTING_TRACE_ENABLED: True}) + + config.add_subscriber(_before_traversal, BeforeTraversal) + _insert_tween(config) + + +def _insert_tween(config): + settings = config.get_settings() + tweens = settings.get("pyramid.tweens") + # If the list is empty, pyramid does not consider the tweens have been + # set explicitly. And if our tween is already there, nothing to do + if not tweens or not tweens.strip(): + # Add our tween just before the default exception handler + config.add_tween(TWEEN_NAME, over=EXCVIEW) + + +def _before_traversal(event): + request = event.request + environ = request.environ + span_name = otel_wsgi.get_default_span_name(environ) + + enabled = environ.get(_ENVIRON_ENABLED_KEY) + if enabled is None: + _logger.warning( + "Opentelemetry pyramid tween 'opentelemetry.ext.pyramid.trace_tween_factory'" + "was not called. Make sure that the tween is included in 'pyramid.tweens' if" + "the tween list was created manually" + ) + return + + if not enabled: + # Tracing not enabled, return + return + + start_time = environ.get(_ENVIRON_STARTTIME_KEY) + + token = context.attach( + propagators.extract(otel_wsgi.get_header_from_environ, environ) + ) + tracer = trace.get_tracer(__name__, __version__) + + attributes = otel_wsgi.collect_request_attributes(environ) + + if request.matched_route: + span_name = request.matched_route.pattern + attributes["http.route"] = request.matched_route.pattern + else: + span_name = otel_wsgi.get_default_span_name(environ) + + span = tracer.start_span( + span_name, + kind=trace.SpanKind.SERVER, + attributes=attributes, + start_time=start_time, + ) + + activation = tracer.use_span(span, end_on_exit=True) + activation.__enter__() + environ[_ENVIRON_ACTIVATION_KEY] = activation + environ[_ENVIRON_SPAN_KEY] = span + environ[_ENVIRON_TOKEN] = token + + +def trace_tween_factory(handler, registry): + settings = registry.settings + enabled = asbool(settings.get(SETTING_TRACE_ENABLED, True)) + + if not enabled: + # If disabled, make a tween that signals to the + # BeforeTraversal subscriber that tracing is disabled + def disabled_tween(request): + request.environ[_ENVIRON_ENABLED_KEY] = False + return handler(request) + + return disabled_tween + + # make a request tracing function + def trace_tween(request): + if disable_trace(request.url, _excluded_hosts, _excluded_paths): + request.environ[_ENVIRON_ENABLED_KEY] = False + # short-circuit when we don't want to trace anything + return handler(request) + + request.environ[_ENVIRON_ENABLED_KEY] = True + request.environ[_ENVIRON_STARTTIME_KEY] = time_ns() + + try: + response = handler(request) + response_or_exception = response + except HTTPException as exc: + # If the exception is a pyramid HTTPException, + # that's still valuable information that isn't necessarily + # a 500. For instance, HTTPFound is a 302. + # As described in docs, Pyramid exceptions are all valid + # response types + response_or_exception = exc + raise + finally: + span = request.environ.get(_ENVIRON_SPAN_KEY) + enabled = request.environ.get(_ENVIRON_ENABLED_KEY) + if not span and enabled: + _logger.warning( + "Pyramid environ's OpenTelemetry span missing." + "If the OpenTelemetry tween was added manually, make sure" + "PyramidInstrumentor().instrument_config(config) is called" + ) + elif enabled: + otel_wsgi.add_response_attributes( + span, + response_or_exception.status, + response_or_exception.headers, + ) + + activation = request.environ.get(_ENVIRON_ACTIVATION_KEY) + + if isinstance(response_or_exception, HTTPException): + activation.__exit__( + type(response_or_exception), + response_or_exception, + getattr(response_or_exception, "__traceback__", None), + ) + else: + activation.__exit__(None, None, None) + + context.detach(request.environ.get(_ENVIRON_TOKEN)) + + return response + + return trace_tween diff --git a/ext/opentelemetry-ext-pyramid/src/opentelemetry/ext/pyramid/version.py b/ext/opentelemetry-ext-pyramid/src/opentelemetry/ext/pyramid/version.py new file mode 100644 index 0000000000..6d4fefa599 --- /dev/null +++ b/ext/opentelemetry-ext-pyramid/src/opentelemetry/ext/pyramid/version.py @@ -0,0 +1,15 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-pyramid/tests/__init__.py b/ext/opentelemetry-ext-pyramid/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ext/opentelemetry-ext-pyramid/tests/pyramid_base_test.py b/ext/opentelemetry-ext-pyramid/tests/pyramid_base_test.py new file mode 100644 index 0000000000..21a6a1ab95 --- /dev/null +++ b/ext/opentelemetry-ext-pyramid/tests/pyramid_base_test.py @@ -0,0 +1,54 @@ +# Copyright The OpenTelemetry Authors +# +# 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 pyramid.httpexceptions as exc +from pyramid.response import Response +from werkzeug.test import Client +from werkzeug.wrappers import BaseResponse + +from opentelemetry.configuration import Configuration + + +class InstrumentationTest: + def setUp(self): # pylint: disable=invalid-name + super().setUp() # pylint: disable=no-member + Configuration._reset() # pylint: disable=protected-access + + @staticmethod + def _hello_endpoint(request): + helloid = int(request.matchdict["helloid"]) + if helloid == 500: + raise exc.HTTPInternalServerError() + return Response("Hello: " + str(helloid)) + + def _common_initialization(self, config): + # pylint: disable=unused-argument + def excluded_endpoint(request): + return Response("excluded") + + # pylint: disable=unused-argument + def excluded2_endpoint(request): + return Response("excluded2") + + config.add_route("hello", "/hello/{helloid}") + config.add_view(self._hello_endpoint, route_name="hello") + config.add_route("excluded_arg", "/excluded/{helloid}") + config.add_view(self._hello_endpoint, route_name="excluded_arg") + config.add_route("excluded", "/excluded_noarg") + config.add_view(excluded_endpoint, route_name="excluded") + config.add_route("excluded2", "/excluded_noarg2") + config.add_view(excluded2_endpoint, route_name="excluded2") + + # pylint: disable=attribute-defined-outside-init + self.client = Client(config.make_wsgi_app(), BaseResponse) diff --git a/ext/opentelemetry-ext-pyramid/tests/test_automatic.py b/ext/opentelemetry-ext-pyramid/tests/test_automatic.py new file mode 100644 index 0000000000..d19da3c2b3 --- /dev/null +++ b/ext/opentelemetry-ext-pyramid/tests/test_automatic.py @@ -0,0 +1,79 @@ +# Copyright The OpenTelemetry Authors +# +# 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 pyramid.config import Configurator + +from opentelemetry.ext.pyramid import PyramidInstrumentor +from opentelemetry.test.test_base import TestBase +from opentelemetry.test.wsgitestutil import WsgiTestBase + +# pylint: disable=import-error +from .pyramid_base_test import InstrumentationTest + + +class TestAutomatic(InstrumentationTest, TestBase, WsgiTestBase): + def setUp(self): + super().setUp() + + PyramidInstrumentor().instrument() + + self.config = Configurator() + + self._common_initialization(self.config) + + def tearDown(self): + super().tearDown() + with self.disable_logging(): + PyramidInstrumentor().uninstrument() + + def test_uninstrument(self): + # pylint: disable=access-member-before-definition + resp = self.client.get("/hello/123") + self.assertEqual(200, resp.status_code) + self.assertEqual([b"Hello: 123"], list(resp.response)) + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + + PyramidInstrumentor().uninstrument() + self.config = Configurator() + + self._common_initialization(self.config) + + resp = self.client.get("/hello/123") + self.assertEqual(200, resp.status_code) + self.assertEqual([b"Hello: 123"], list(resp.response)) + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + + def test_tween_list(self): + tween_list = "pyramid.tweens.excview_tween_factory" + config = Configurator(settings={"pyramid.tweens": tween_list}) + self._common_initialization(config) + resp = self.client.get("/hello/123") + self.assertEqual(200, resp.status_code) + self.assertEqual([b"Hello: 123"], list(resp.response)) + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + + PyramidInstrumentor().uninstrument() + + self.config = Configurator() + + self._common_initialization(self.config) + + resp = self.client.get("/hello/123") + self.assertEqual(200, resp.status_code) + self.assertEqual([b"Hello: 123"], list(resp.response)) + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) diff --git a/ext/opentelemetry-ext-pyramid/tests/test_programmatic.py b/ext/opentelemetry-ext-pyramid/tests/test_programmatic.py new file mode 100644 index 0000000000..b1ecbb38bb --- /dev/null +++ b/ext/opentelemetry-ext-pyramid/tests/test_programmatic.py @@ -0,0 +1,194 @@ +# Copyright The OpenTelemetry Authors +# +# 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 unittest.mock import patch + +from pyramid.config import Configurator + +from opentelemetry import trace +from opentelemetry.ext.pyramid import PyramidInstrumentor +from opentelemetry.test.test_base import TestBase +from opentelemetry.test.wsgitestutil import WsgiTestBase + +# pylint: disable=import-error +from .pyramid_base_test import InstrumentationTest + + +def expected_attributes(override_attributes): + default_attributes = { + "component": "http", + "http.method": "GET", + "http.server_name": "localhost", + "http.scheme": "http", + "host.port": 80, + "http.host": "localhost", + "http.target": "/", + "http.flavor": "1.1", + "http.status_text": "OK", + "http.status_code": 200, + } + for key, val in override_attributes.items(): + default_attributes[key] = val + return default_attributes + + +class TestProgrammatic(InstrumentationTest, TestBase, WsgiTestBase): + def setUp(self): + super().setUp() + config = Configurator() + PyramidInstrumentor().instrument_config(config) + + self.config = config + + self._common_initialization(self.config) + + def tearDown(self): + super().tearDown() + with self.disable_logging(): + PyramidInstrumentor().uninstrument_config(self.config) + + def test_uninstrument(self): + resp = self.client.get("/hello/123") + self.assertEqual(200, resp.status_code) + self.assertEqual([b"Hello: 123"], list(resp.response)) + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + + PyramidInstrumentor().uninstrument_config(self.config) + # Need to remake the WSGI app export + self._common_initialization(self.config) + + resp = self.client.get("/hello/123") + self.assertEqual(200, resp.status_code) + self.assertEqual([b"Hello: 123"], list(resp.response)) + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + + def test_simple(self): + expected_attrs = expected_attributes( + {"http.target": "/hello/123", "http.route": "/hello/{helloid}"} + ) + self.client.get("/hello/123") + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + self.assertEqual(span_list[0].name, "/hello/{helloid}") + self.assertEqual(span_list[0].kind, trace.SpanKind.SERVER) + self.assertEqual(span_list[0].attributes, expected_attrs) + + def test_404(self): + expected_attrs = expected_attributes( + { + "http.method": "POST", + "http.target": "/bye", + "http.status_text": "Not Found", + "http.status_code": 404, + } + ) + + resp = self.client.post("/bye") + self.assertEqual(404, resp.status_code) + resp.close() + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + self.assertEqual(span_list[0].name, "/bye") + self.assertEqual(span_list[0].kind, trace.SpanKind.SERVER) + self.assertEqual(span_list[0].attributes, expected_attrs) + + def test_internal_error(self): + expected_attrs = expected_attributes( + { + "http.target": "/hello/500", + "http.route": "/hello/{helloid}", + "http.status_text": "Internal Server Error", + "http.status_code": 500, + } + ) + resp = self.client.get("/hello/500") + self.assertEqual(500, resp.status_code) + resp.close() + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + self.assertEqual(span_list[0].name, "/hello/{helloid}") + self.assertEqual(span_list[0].kind, trace.SpanKind.SERVER) + self.assertEqual(span_list[0].attributes, expected_attrs) + + def test_tween_list(self): + tween_list = "opentelemetry.ext.pyramid.trace_tween_factory\npyramid.tweens.excview_tween_factory" + config = Configurator(settings={"pyramid.tweens": tween_list}) + PyramidInstrumentor().instrument_config(config) + self._common_initialization(config) + + resp = self.client.get("/hello/123") + self.assertEqual(200, resp.status_code) + self.assertEqual([b"Hello: 123"], list(resp.response)) + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + + PyramidInstrumentor().uninstrument_config(config) + # Need to remake the WSGI app export + self._common_initialization(config) + + resp = self.client.get("/hello/123") + self.assertEqual(200, resp.status_code) + self.assertEqual([b"Hello: 123"], list(resp.response)) + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + + @patch("opentelemetry.ext.pyramid.callbacks._logger") + def test_warnings(self, mock_logger): + tween_list = "pyramid.tweens.excview_tween_factory" + config = Configurator(settings={"pyramid.tweens": tween_list}) + PyramidInstrumentor().instrument_config(config) + self._common_initialization(config) + + self.client.get("/hello/123") + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 0) + self.assertEqual(mock_logger.warning.called, True) + + mock_logger.warning.called = False + + tween_list = "opentelemetry.ext.pyramid.trace_tween_factory" + config = Configurator(settings={"pyramid.tweens": tween_list}) + self._common_initialization(config) + + self.client.get("/hello/123") + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 0) + self.assertEqual(mock_logger.warning.called, True) + + @patch( + "opentelemetry.ext.pyramid.callbacks._excluded_hosts", + ["http://localhost/excluded_arg/123"], + ) + @patch( + "opentelemetry.ext.pyramid.callbacks._excluded_paths", + ["excluded_noarg"], + ) + def test_exclude_lists(self): + self.client.get("/excluded_arg/123") + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 0) + + self.client.get("/excluded_arg/125") + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + + self.client.get("/excluded_noarg") + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + + self.client.get("/excluded_noarg2") + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) diff --git a/ext/opentelemetry-ext-redis/setup.cfg b/ext/opentelemetry-ext-redis/setup.cfg index 2694d85d52..f6fbbafaaa 100644 --- a/ext/opentelemetry-ext-redis/setup.cfg +++ b/ext/opentelemetry-ext-redis/setup.cfg @@ -40,15 +40,15 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9.dev0 - opentelemetry-auto-instrumentation == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 redis >= 2.6 wrapt >= 1.12.1 [options.extras_require] test = - opentelemetry-test == 0.9.dev0 - opentelemetry-sdk == 0.9.dev0 + opentelemetry-test == 0.10.dev0 + opentelemetry-sdk == 0.10.dev0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-redis/src/opentelemetry/ext/redis/__init__.py b/ext/opentelemetry-ext-redis/src/opentelemetry/ext/redis/__init__.py index e653936ca5..0b7f6e28eb 100644 --- a/ext/opentelemetry-ext-redis/src/opentelemetry/ext/redis/__init__.py +++ b/ext/opentelemetry-ext-redis/src/opentelemetry/ext/redis/__init__.py @@ -16,7 +16,7 @@ Instrument `redis`_ to report Redis queries. There are two options for instrumenting code. The first option is to use the -``opentelemetry-auto-instrumentation`` executable which will automatically +``opentelemetry-instrumentation`` executable which will automatically instrument your Redis client. The second is to programmatically enable instrumentation via the following code: @@ -49,12 +49,13 @@ from wrapt import ObjectProxy, wrap_function_wrapper from opentelemetry import trace -from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor from opentelemetry.ext.redis.util import ( _extract_conn_attributes, _format_command_args, ) from opentelemetry.ext.redis.version import __version__ +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.instrumentation.utils import unwrap _DEFAULT_SERVICE = "redis" _RAWCMD = "db.statement" @@ -68,12 +69,6 @@ def _set_connection_attributes(span, conn): span.set_attribute(key, value) -def _unwrap(obj, attr): - func = getattr(obj, attr, None) - if isinstance(func, ObjectProxy) and hasattr(func, "__wrapped__"): - setattr(obj, attr, func.__wrapped__) - - def _traced_execute_command(func, instance, args, kwargs): tracer = getattr(redis, "_opentelemetry_tracer") query = _format_command_args(args) @@ -145,19 +140,19 @@ def _instrument(self, **kwargs): def _uninstrument(self, **kwargs): if redis.VERSION < (3, 0, 0): - _unwrap(redis.StrictRedis, "execute_command") - _unwrap(redis.StrictRedis, "pipeline") - _unwrap(redis.Redis, "pipeline") - _unwrap( + unwrap(redis.StrictRedis, "execute_command") + unwrap(redis.StrictRedis, "pipeline") + unwrap(redis.Redis, "pipeline") + unwrap( redis.client.BasePipeline, # pylint:disable=no-member "execute", ) - _unwrap( + unwrap( redis.client.BasePipeline, # pylint:disable=no-member "immediate_execute_command", ) else: - _unwrap(redis.Redis, "execute_command") - _unwrap(redis.Redis, "pipeline") - _unwrap(redis.client.Pipeline, "execute") - _unwrap(redis.client.Pipeline, "immediate_execute_command") + unwrap(redis.Redis, "execute_command") + unwrap(redis.Redis, "pipeline") + unwrap(redis.client.Pipeline, "execute") + unwrap(redis.client.Pipeline, "immediate_execute_command") diff --git a/ext/opentelemetry-ext-redis/src/opentelemetry/ext/redis/version.py b/ext/opentelemetry-ext-redis/src/opentelemetry/ext/redis/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-redis/src/opentelemetry/ext/redis/version.py +++ b/ext/opentelemetry-ext-redis/src/opentelemetry/ext/redis/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-requests/setup.cfg b/ext/opentelemetry-ext-requests/setup.cfg index ca5dc5ceba..7bb84b42ba 100644 --- a/ext/opentelemetry-ext-requests/setup.cfg +++ b/ext/opentelemetry-ext-requests/setup.cfg @@ -40,13 +40,13 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9.dev0 - opentelemetry-auto-instrumentation == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 requests ~= 2.0 [options.extras_require] test = - opentelemetry-test == 0.9.dev0 + opentelemetry-test == 0.10.dev0 httpretty ~= 1.0 [options.packages.find] diff --git a/ext/opentelemetry-ext-requests/src/opentelemetry/ext/requests/__init__.py b/ext/opentelemetry-ext-requests/src/opentelemetry/ext/requests/__init__.py index 65bc21370f..0437c1bf54 100644 --- a/ext/opentelemetry-ext-requests/src/opentelemetry/ext/requests/__init__.py +++ b/ext/opentelemetry-ext-requests/src/opentelemetry/ext/requests/__init__.py @@ -50,9 +50,10 @@ from requests.sessions import Session from opentelemetry import context, propagators, trace -from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor from opentelemetry.ext.requests.version import __version__ -from opentelemetry.trace import SpanKind +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.instrumentation.utils import http_status_to_canonical_code +from opentelemetry.trace import SpanKind, get_tracer from opentelemetry.trace.status import Status, StatusCanonicalCode @@ -95,8 +96,9 @@ def instrumented_request(self, method, url, *args, **kwargs): span.set_attribute("http.method", method.upper()) span.set_attribute("http.url", url) - headers = kwargs.setdefault("headers", {}) + headers = kwargs.get("headers", {}) or {} propagators.inject(type(headers).__setitem__, headers) + kwargs["headers"] = headers try: result = wrapped( @@ -115,7 +117,7 @@ def instrumented_request(self, method, url, *args, **kwargs): span.set_attribute("http.status_code", result.status_code) span.set_attribute("http.status_text", result.reason) span.set_status( - Status(_http_status_to_canonical_code(result.status_code)) + Status(http_status_to_canonical_code(result.status_code)) ) if span_callback is not None: @@ -146,37 +148,6 @@ def _uninstrument(): Session.request = original -def _http_status_to_canonical_code(code: int, allow_redirect: bool = True): - # pylint:disable=too-many-branches,too-many-return-statements - if code < 100: - return StatusCanonicalCode.UNKNOWN - if code <= 299: - return StatusCanonicalCode.OK - if code <= 399: - if allow_redirect: - return StatusCanonicalCode.OK - return StatusCanonicalCode.DEADLINE_EXCEEDED - if code <= 499: - if code == 401: # HTTPStatus.UNAUTHORIZED: - return StatusCanonicalCode.UNAUTHENTICATED - if code == 403: # HTTPStatus.FORBIDDEN: - return StatusCanonicalCode.PERMISSION_DENIED - if code == 404: # HTTPStatus.NOT_FOUND: - return StatusCanonicalCode.NOT_FOUND - if code == 429: # HTTPStatus.TOO_MANY_REQUESTS: - return StatusCanonicalCode.RESOURCE_EXHAUSTED - return StatusCanonicalCode.INVALID_ARGUMENT - if code <= 599: - if code == 501: # HTTPStatus.NOT_IMPLEMENTED: - return StatusCanonicalCode.UNIMPLEMENTED - if code == 503: # HTTPStatus.SERVICE_UNAVAILABLE: - return StatusCanonicalCode.UNAVAILABLE - if code == 504: # HTTPStatus.GATEWAY_TIMEOUT: - return StatusCanonicalCode.DEADLINE_EXCEEDED - return StatusCanonicalCode.INTERNAL - return StatusCanonicalCode.UNKNOWN - - def _exception_to_canonical_code(exc: Exception) -> StatusCanonicalCode: if isinstance( exc, diff --git a/ext/opentelemetry-ext-requests/src/opentelemetry/ext/requests/version.py b/ext/opentelemetry-ext-requests/src/opentelemetry/ext/requests/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-requests/src/opentelemetry/ext/requests/version.py +++ b/ext/opentelemetry-ext-requests/src/opentelemetry/ext/requests/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-requests/tests/test_requests_integration.py b/ext/opentelemetry-ext-requests/tests/test_requests_integration.py index 0ad5b9d19d..75a63f3cf8 100644 --- a/ext/opentelemetry-ext-requests/tests/test_requests_integration.py +++ b/ext/opentelemetry-ext-requests/tests/test_requests_integration.py @@ -228,6 +228,10 @@ def test_custom_tracer_provider(self): self.assertIs(span.resource, resource) + def test_if_headers_equals_none(self): + result = requests.get(self.URL, headers=None) + self.assertEqual(result.text, "Hello!") + @mock.patch("requests.Session.send", side_effect=requests.RequestException) def test_requests_exception_without_response(self, *_, **__): diff --git a/ext/opentelemetry-ext-sqlalchemy/setup.cfg b/ext/opentelemetry-ext-sqlalchemy/setup.cfg index dfbf655e6d..c964b1327b 100644 --- a/ext/opentelemetry-ext-sqlalchemy/setup.cfg +++ b/ext/opentelemetry-ext-sqlalchemy/setup.cfg @@ -40,14 +40,14 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9.dev0 - opentelemetry-auto-instrumentation == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 wrapt >= 1.11.2 sqlalchemy [options.extras_require] test = - opentelemetry-sdk == 0.9.dev0 + opentelemetry-sdk == 0.10.dev0 pytest [options.packages.find] diff --git a/ext/opentelemetry-ext-sqlalchemy/src/opentelemetry/ext/sqlalchemy/__init__.py b/ext/opentelemetry-ext-sqlalchemy/src/opentelemetry/ext/sqlalchemy/__init__.py index 05b384429b..05d5e625ef 100644 --- a/ext/opentelemetry-ext-sqlalchemy/src/opentelemetry/ext/sqlalchemy/__init__.py +++ b/ext/opentelemetry-ext-sqlalchemy/src/opentelemetry/ext/sqlalchemy/__init__.py @@ -16,7 +16,7 @@ Instrument `sqlalchemy`_ to report SQL queries. There are two options for instrumenting code. The first option is to use -the ``opentelemetry-auto-instrumentation`` executable which will automatically +the ``opentelemetry-instrument`` executable which will automatically instrument your SQLAlchemy engine. The second is to programmatically enable instrumentation via the following code: @@ -47,22 +47,13 @@ import wrapt from wrapt import wrap_function_wrapper as _w -from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor from opentelemetry.ext.sqlalchemy.engine import ( EngineTracer, _get_tracer, _wrap_create_engine, ) - - -def _unwrap(obj, attr): - func = getattr(obj, attr, None) - if ( - func - and isinstance(func, wrapt.ObjectProxy) - and hasattr(func, "__wrapped__") - ): - setattr(obj, attr, func.__wrapped__) +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.instrumentation.utils import unwrap class SQLAlchemyInstrumentor(BaseInstrumentor): @@ -96,5 +87,5 @@ def _instrument(self, **kwargs): return None def _uninstrument(self, **kwargs): - _unwrap(sqlalchemy, "create_engine") - _unwrap(sqlalchemy.engine, "create_engine") + unwrap(sqlalchemy, "create_engine") + unwrap(sqlalchemy.engine, "create_engine") diff --git a/ext/opentelemetry-ext-sqlalchemy/src/opentelemetry/ext/sqlalchemy/version.py b/ext/opentelemetry-ext-sqlalchemy/src/opentelemetry/ext/sqlalchemy/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-sqlalchemy/src/opentelemetry/ext/sqlalchemy/version.py +++ b/ext/opentelemetry-ext-sqlalchemy/src/opentelemetry/ext/sqlalchemy/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-sqlite3/setup.cfg b/ext/opentelemetry-ext-sqlite3/setup.cfg index b32dd1cf68..a1128ca88f 100644 --- a/ext/opentelemetry-ext-sqlite3/setup.cfg +++ b/ext/opentelemetry-ext-sqlite3/setup.cfg @@ -40,14 +40,14 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9.dev0 - opentelemetry-ext-dbapi == 0.9.dev0 - opentelemetry-auto-instrumentation == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-ext-dbapi == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] test = - opentelemetry-test == 0.9.dev0 + opentelemetry-test == 0.10.dev0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-sqlite3/src/opentelemetry/ext/sqlite3/__init__.py b/ext/opentelemetry-ext-sqlite3/src/opentelemetry/ext/sqlite3/__init__.py index 5c9a009360..174fbba116 100644 --- a/ext/opentelemetry-ext-sqlite3/src/opentelemetry/ext/sqlite3/__init__.py +++ b/ext/opentelemetry-ext-sqlite3/src/opentelemetry/ext/sqlite3/__init__.py @@ -45,9 +45,9 @@ import sqlite3 import typing -from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor from opentelemetry.ext import dbapi from opentelemetry.ext.sqlite3.version import __version__ +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.trace import TracerProvider, get_tracer diff --git a/ext/opentelemetry-ext-sqlite3/src/opentelemetry/ext/sqlite3/version.py b/ext/opentelemetry-ext-sqlite3/src/opentelemetry/ext/sqlite3/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-sqlite3/src/opentelemetry/ext/sqlite3/version.py +++ b/ext/opentelemetry-ext-sqlite3/src/opentelemetry/ext/sqlite3/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-system-metrics/CHANGELOG.md b/ext/opentelemetry-ext-system-metrics/CHANGELOG.md index 2f3c3ab382..12d51bb800 100644 --- a/ext/opentelemetry-ext-system-metrics/CHANGELOG.md +++ b/ext/opentelemetry-ext-system-metrics/CHANGELOG.md @@ -2,4 +2,8 @@ ## Unreleased +## 0.9b0 + +Released 2020-06-10 + - Initial release (https://github.com/open-telemetry/opentelemetry-python/pull/652) diff --git a/ext/opentelemetry-ext-system-metrics/setup.cfg b/ext/opentelemetry-ext-system-metrics/setup.cfg index 25515eaf0d..063e7e91b2 100644 --- a/ext/opentelemetry-ext-system-metrics/setup.cfg +++ b/ext/opentelemetry-ext-system-metrics/setup.cfg @@ -40,12 +40,12 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9.dev0 + opentelemetry-api == 0.10.dev0 psutil ~= 5.7.0 [options.extras_require] test = - opentelemetry-test == 0.9.dev0 + opentelemetry-test == 0.10.dev0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-system-metrics/src/opentelemetry/ext/system_metrics/__init__.py b/ext/opentelemetry-ext-system-metrics/src/opentelemetry/ext/system_metrics/__init__.py index 88f36f4ac4..10b0979557 100644 --- a/ext/opentelemetry-ext-system-metrics/src/opentelemetry/ext/system_metrics/__init__.py +++ b/ext/opentelemetry-ext-system-metrics/src/opentelemetry/ext/system_metrics/__init__.py @@ -28,9 +28,12 @@ .. code:: python + from opentelemetry import metrics from opentelemetry.ext.system_metrics import SystemMetrics + from opentelemetry.sdk.metrics import MeterProvider, from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter + metrics.set_meter_provider(MeterProvider()) exporter = ConsoleMetricsExporter() SystemMetrics(exporter) @@ -58,6 +61,7 @@ import psutil from opentelemetry import metrics +from opentelemetry.sdk.metrics import ValueObserver from opentelemetry.sdk.metrics.export import MetricsExporter from opentelemetry.sdk.metrics.export.controller import PushController @@ -106,6 +110,7 @@ def __init__( description="System memory", unit="bytes", value_type=int, + observer_type=ValueObserver, ) self.meter.register_observer( @@ -114,6 +119,7 @@ def __init__( description="System CPU", unit="seconds", value_type=float, + observer_type=ValueObserver, ) self.meter.register_observer( @@ -122,6 +128,7 @@ def __init__( description="System network bytes", unit="bytes", value_type=int, + observer_type=ValueObserver, ) self.meter.register_observer( @@ -130,6 +137,7 @@ def __init__( description="Runtime memory", unit="bytes", value_type=int, + observer_type=ValueObserver, ) self.meter.register_observer( @@ -138,6 +146,7 @@ def __init__( description="Runtime CPU", unit="seconds", value_type=float, + observer_type=ValueObserver, ) self.meter.register_observer( @@ -146,9 +155,10 @@ def __init__( description="Runtime: gc objects", unit="objects", value_type=int, + observer_type=ValueObserver, ) - def _get_system_memory(self, observer: metrics.Observer) -> None: + def _get_system_memory(self, observer: metrics.ValueObserver) -> None: """Observer callback for memory available Args: @@ -161,7 +171,7 @@ def _get_system_memory(self, observer: metrics.Observer) -> None: getattr(system_memory, metric), self._system_memory_labels ) - def _get_system_cpu(self, observer: metrics.Observer) -> None: + def _get_system_cpu(self, observer: metrics.ValueObserver) -> None: """Observer callback for system cpu Args: @@ -174,7 +184,7 @@ def _get_system_cpu(self, observer: metrics.Observer) -> None: getattr(cpu_times, _type), self._system_cpu_labels ) - def _get_network_bytes(self, observer: metrics.Observer) -> None: + def _get_network_bytes(self, observer: metrics.ValueObserver) -> None: """Observer callback for network bytes Args: @@ -187,7 +197,7 @@ def _get_network_bytes(self, observer: metrics.Observer) -> None: getattr(net_io, _type), self._network_bytes_labels ) - def _get_runtime_memory(self, observer: metrics.Observer) -> None: + def _get_runtime_memory(self, observer: metrics.ValueObserver) -> None: """Observer callback for runtime memory Args: @@ -200,7 +210,7 @@ def _get_runtime_memory(self, observer: metrics.Observer) -> None: getattr(proc_memory, _type), self._runtime_memory_labels ) - def _get_runtime_cpu(self, observer: metrics.Observer) -> None: + def _get_runtime_cpu(self, observer: metrics.ValueObserver) -> None: """Observer callback for runtime CPU Args: @@ -213,7 +223,7 @@ def _get_runtime_cpu(self, observer: metrics.Observer) -> None: getattr(proc_cpu, _type), self._runtime_cpu_labels ) - def _get_runtime_gc_count(self, observer: metrics.Observer) -> None: + def _get_runtime_gc_count(self, observer: metrics.ValueObserver) -> None: """Observer callback for garbage collection Args: diff --git a/ext/opentelemetry-ext-system-metrics/src/opentelemetry/ext/system_metrics/version.py b/ext/opentelemetry-ext-system-metrics/src/opentelemetry/ext/system_metrics/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-system-metrics/src/opentelemetry/ext/system_metrics/version.py +++ b/ext/opentelemetry-ext-system-metrics/src/opentelemetry/ext/system_metrics/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-system-metrics/tests/test_system_metrics.py b/ext/opentelemetry-ext-system-metrics/tests/test_system_metrics.py index 70ead2c515..b2d6bab401 100644 --- a/ext/opentelemetry-ext-system-metrics/tests/test_system_metrics.py +++ b/ext/opentelemetry-ext-system-metrics/tests/test_system_metrics.py @@ -54,7 +54,7 @@ def _assert_metrics(self, observer_name, system_metrics, expected): ): if ( metric.labels in expected - and metric.metric.name == observer_name + and metric.instrument.name == observer_name ): self.assertEqual( metric.aggregator.checkpoint.last, expected[metric.labels], diff --git a/ext/opentelemetry-ext-wsgi/setup.cfg b/ext/opentelemetry-ext-wsgi/setup.cfg index 288ebcaa64..48b0697d11 100644 --- a/ext/opentelemetry-ext-wsgi/setup.cfg +++ b/ext/opentelemetry-ext-wsgi/setup.cfg @@ -40,11 +40,12 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-instrumentation == 0.10.dev0 [options.extras_require] test = - opentelemetry-test == 0.9.dev0 + opentelemetry-test == 0.10.dev0 [options.packages.find] where = src diff --git a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py index 61e8126c19..c5b0216869 100644 --- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py +++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py @@ -61,7 +61,7 @@ def hello(): from opentelemetry import context, propagators, trace from opentelemetry.ext.wsgi.version import __version__ -from opentelemetry.trace.propagation import get_span_from_context +from opentelemetry.instrumentation.utils import http_status_to_canonical_code from opentelemetry.trace.status import Status, StatusCanonicalCode _HTTP_VERSION_PREFIX = "HTTP/" @@ -87,37 +87,6 @@ def setifnotnone(dic, key, value): dic[key] = value -def http_status_to_canonical_code(code: int, allow_redirect: bool = True): - # pylint:disable=too-many-branches,too-many-return-statements - if code < 100: - return StatusCanonicalCode.UNKNOWN - if code <= 299: - return StatusCanonicalCode.OK - if code <= 399: - if allow_redirect: - return StatusCanonicalCode.OK - return StatusCanonicalCode.DEADLINE_EXCEEDED - if code <= 499: - if code == 401: # HTTPStatus.UNAUTHORIZED: - return StatusCanonicalCode.UNAUTHENTICATED - if code == 403: # HTTPStatus.FORBIDDEN: - return StatusCanonicalCode.PERMISSION_DENIED - if code == 404: # HTTPStatus.NOT_FOUND: - return StatusCanonicalCode.NOT_FOUND - if code == 429: # HTTPStatus.TOO_MANY_REQUESTS: - return StatusCanonicalCode.RESOURCE_EXHAUSTED - return StatusCanonicalCode.INVALID_ARGUMENT - if code <= 599: - if code == 501: # HTTPStatus.NOT_IMPLEMENTED: - return StatusCanonicalCode.UNIMPLEMENTED - if code == 503: # HTTPStatus.SERVICE_UNAVAILABLE: - return StatusCanonicalCode.UNAVAILABLE - if code == 504: # HTTPStatus.GATEWAY_TIMEOUT: - return StatusCanonicalCode.DEADLINE_EXCEEDED - return StatusCanonicalCode.INTERNAL - return StatusCanonicalCode.UNKNOWN - - def collect_request_attributes(environ): """Collects HTTP request attributes from the PEP3333-conforming WSGI environ and returns a dictionary to be used as span creation attributes.""" diff --git a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/version.py b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/version.py +++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-ext-zipkin/setup.cfg b/ext/opentelemetry-ext-zipkin/setup.cfg index 809db51237..89d40aaeaa 100644 --- a/ext/opentelemetry-ext-zipkin/setup.cfg +++ b/ext/opentelemetry-ext-zipkin/setup.cfg @@ -41,8 +41,11 @@ package_dir= packages=find_namespace: install_requires = requests ~= 2.7 - opentelemetry-api == 0.9.dev0 - opentelemetry-sdk == 0.9.dev0 + opentelemetry-api == 0.10.dev0 + opentelemetry-sdk == 0.10.dev0 [options.packages.find] where = src + +[options.extras_require] +test = diff --git a/ext/opentelemetry-ext-zipkin/src/opentelemetry/ext/zipkin/version.py b/ext/opentelemetry-ext-zipkin/src/opentelemetry/ext/zipkin/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/ext/opentelemetry-ext-zipkin/src/opentelemetry/ext/zipkin/version.py +++ b/ext/opentelemetry-ext-zipkin/src/opentelemetry/ext/zipkin/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-instrumentation-starlette/CHANGELOG.md b/ext/opentelemetry-instrumentation-starlette/CHANGELOG.md new file mode 100644 index 0000000000..f7132ca830 --- /dev/null +++ b/ext/opentelemetry-instrumentation-starlette/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## Unreleased + +- Initial release ([#777](https://github.com/open-telemetry/opentelemetry-python/pull/777)) \ No newline at end of file diff --git a/ext/opentelemetry-instrumentation-starlette/README.rst b/ext/opentelemetry-instrumentation-starlette/README.rst new file mode 100644 index 0000000000..1d05c0b717 --- /dev/null +++ b/ext/opentelemetry-instrumentation-starlette/README.rst @@ -0,0 +1,45 @@ +OpenTelemetry Starlette Instrumentation +======================================= + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-starlette.svg + :target: https://pypi.org/project/opentelemetry-instrumentation-starlette/ + + +This library provides automatic and manual instrumentation of Starlette web frameworks, +instrumenting http requests served by applications utilizing the framework. + +auto-instrumentation using the opentelemetry-instrumentation package is also supported. + +Installation +------------ + +:: + + pip install opentelemetry-instrumentation-starlette + + +Usage +----- + +.. code-block:: python + + from opentelemetry.instrumentation.starlette import StarletteInstrumentor + from starlette import applications + from starlette.responses import PlainTextResponse + from starlette.routing import Route + + def home(request): + return PlainTextResponse("hi") + + app = applications.Starlette( + routes=[Route("/foobar", home)] + ) + StarletteInstrumentor.instrument_app(app) + + +References +---------- + +* `OpenTelemetry Project `_ diff --git a/ext/opentelemetry-instrumentation-starlette/setup.cfg b/ext/opentelemetry-instrumentation-starlette/setup.cfg new file mode 100644 index 0000000000..7659c9fadb --- /dev/null +++ b/ext/opentelemetry-instrumentation-starlette/setup.cfg @@ -0,0 +1,55 @@ +# Copyright The OpenTelemetry Authors +# +# 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. +# +[metadata] +name = opentelemetry-instrumentation-starlette +description = OpenTelemetry Starlette Instrumentation +long_description = file: README.rst +long_description_content_type = text/x-rst +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +url = https://github.com/open-telemetry/opentelemetry-python/ext/opentelemetry-instrumentation-starlette +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + +[options] +python_requires = >=3.6 +package_dir= + =src +packages=find_namespace: +install_requires = + opentelemetry-api == 0.10.dev0 + opentelemetry-ext-asgi == 0.10.dev0 + +[options.entry_points] +opentelemetry_instrumentor = + starlette = opentelemetry.instrumentation.starlette:StarletteInstrumentor + +[options.extras_require] +test = + opentelemetry-test == 0.10.dev0 + starlette ~= 0.13.0 + requests ~= 2.23.0 # needed for testclient + +[options.packages.find] +where = src diff --git a/ext/opentelemetry-instrumentation-starlette/setup.py b/ext/opentelemetry-instrumentation-starlette/setup.py new file mode 100644 index 0000000000..0232a6f448 --- /dev/null +++ b/ext/opentelemetry-instrumentation-starlette/setup.py @@ -0,0 +1,31 @@ +# Copyright The OpenTelemetry Authors +# +# 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 setuptools + +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join( + BASE_DIR, + "src", + "opentelemetry", + "instrumentation", + "starlette", + "version.py", +) +PACKAGE_INFO = {} +with open(VERSION_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup(version=PACKAGE_INFO["__version__"]) diff --git a/ext/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py b/ext/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py new file mode 100644 index 0000000000..197a38d759 --- /dev/null +++ b/ext/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py @@ -0,0 +1,82 @@ +# Copyright The OpenTelemetry Authors +# +# 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 typing import Optional + +from starlette import applications +from starlette.routing import Match + +from opentelemetry.ext.asgi import OpenTelemetryMiddleware +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.instrumentation.starlette.version import __version__ # noqa + + +class StarletteInstrumentor(BaseInstrumentor): + """An instrumentor for starlette + + See `BaseInstrumentor` + """ + + _original_starlette = None + + @staticmethod + def instrument_app(app: applications.Starlette): + """Instrument a previously instrumented Starlette application. + """ + if not getattr(app, "is_instrumented_by_opentelemetry", False): + app.add_middleware( + OpenTelemetryMiddleware, + span_details_callback=_get_route_details, + ) + app.is_instrumented_by_opentelemetry = True + + def _instrument(self, **kwargs): + self._original_starlette = applications.Starlette + applications.Starlette = _InstrumentedStarlette + + def _uninstrument(self, **kwargs): + applications.Starlette = self._original_starlette + + +class _InstrumentedStarlette(applications.Starlette): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.add_middleware( + OpenTelemetryMiddleware, span_details_callback=_get_route_details + ) + + +def _get_route_details(scope): + """Callback to retrieve the starlette route being served. + + TODO: there is currently no way to retrieve http.route from + a starlette application from scope. + + See: https://github.com/encode/starlette/pull/804 + """ + app = scope["app"] + route = None + for starlette_route in app.routes: + match, _ = starlette_route.matches(scope) + if match == Match.FULL: + route = starlette_route.path + break + if match == Match.PARTIAL: + route = starlette_route.path + # method only exists for http, if websocket + # leave it blank. + span_name = route or scope.get("method", "") + attributes = {} + if route: + attributes["http.route"] = route + return span_name, attributes diff --git a/ext/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/version.py b/ext/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/version.py new file mode 100644 index 0000000000..6d4fefa599 --- /dev/null +++ b/ext/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/version.py @@ -0,0 +1,15 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +__version__ = "0.10.dev0" diff --git a/ext/opentelemetry-instrumentation-starlette/tests/__init__.py b/ext/opentelemetry-instrumentation-starlette/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ext/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py b/ext/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py new file mode 100644 index 0000000000..a49db07c9a --- /dev/null +++ b/ext/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py @@ -0,0 +1,102 @@ +# Copyright The OpenTelemetry Authors +# +# 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 starlette import applications +from starlette.responses import PlainTextResponse +from starlette.routing import Route +from starlette.testclient import TestClient + +import opentelemetry.instrumentation.starlette as otel_starlette +from opentelemetry.test.test_base import TestBase + + +class TestStarletteManualInstrumentation(TestBase): + def _create_app(self): + app = self._create_starlette_app() + self._instrumentor.instrument_app(app) + return app + + def setUp(self): + super().setUp() + self._instrumentor = otel_starlette.StarletteInstrumentor() + self._app = self._create_app() + self._client = TestClient(self._app) + + def test_basic_starlette_call(self): + self._client.get("/foobar") + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 3) + for span in spans: + self.assertIn("/foobar", span.name) + + def test_starlette_route_attribute_added(self): + """Ensure that starlette routes are used as the span name.""" + self._client.get("/user/123") + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 3) + for span in spans: + self.assertIn("/user/{username}", span.name) + self.assertEqual( + spans[-1].attributes["http.route"], "/user/{username}" + ) + # ensure that at least one attribute that is populated by + # the asgi instrumentation is successfully feeding though. + self.assertEqual(spans[-1].attributes["http.flavor"], "1.1") + + @staticmethod + def _create_starlette_app(): + def home(_): + return PlainTextResponse("hi") + + app = applications.Starlette( + routes=[Route("/foobar", home), Route("/user/{username}", home)] + ) + return app + + +class TestAutoInstrumentation(TestStarletteManualInstrumentation): + """Test the auto-instrumented variant + + Extending the manual instrumentation as most test cases apply + to both. + """ + + def _create_app(self): + # instrumentation is handled by the instrument call + self._instrumentor.instrument() + return self._create_starlette_app() + + def tearDown(self): + self._instrumentor.uninstrument() + super().tearDown() + + +class TestAutoInstrumentationLogic(unittest.TestCase): + def test_instrumentation(self): + """Verify that instrumentation methods are instrumenting and + removing as expected. + """ + instrumentor = otel_starlette.StarletteInstrumentor() + original = applications.Starlette + instrumentor.instrument() + try: + instrumented = applications.Starlette + self.assertIsNot(original, instrumented) + finally: + instrumentor.uninstrument() + + should_be_original = applications.Starlette + self.assertIs(original, should_be_original) diff --git a/opentelemetry-api/CHANGELOG.md b/opentelemetry-api/CHANGELOG.md index a67c840190..10c08c8574 100644 --- a/opentelemetry-api/CHANGELOG.md +++ b/opentelemetry-api/CHANGELOG.md @@ -2,10 +2,20 @@ ## Unreleased +## 0.9b0 + +Released 2020-06-10 + - Move stateful from Meter to MeterProvider ([#751](https://github.com/open-telemetry/opentelemetry-python/pull/751)) - Rename Measure to ValueRecorder in metrics ([#761](https://github.com/open-telemetry/opentelemetry-python/pull/761)) +- Adding trace.get_current_span, Removing Tracer.get_current_span + ([#552](https://github.com/open-telemetry/opentelemetry-python/pull/552)) +- Rename Observer to ValueObserver + ([#764](https://github.com/open-telemetry/opentelemetry-python/pull/764)) +- Add SumObserver and UpDownSumObserver in metrics + ([#789](https://github.com/open-telemetry/opentelemetry-python/pull/789)) ## 0.8b0 diff --git a/opentelemetry-api/setup.cfg b/opentelemetry-api/setup.cfg index d6d68a710d..8300be0d0a 100644 --- a/opentelemetry-api/setup.cfg +++ b/opentelemetry-api/setup.cfg @@ -57,3 +57,6 @@ opentelemetry_meter_provider = default_meter_provider = opentelemetry.metrics:DefaultMeterProvider opentelemetry_tracer_provider = default_tracer_provider = opentelemetry.trace:DefaultTracerProvider + +[options.extras_require] +test = diff --git a/opentelemetry-api/src/opentelemetry/__init__.pyi b/opentelemetry-api/src/opentelemetry/__init__.pyi new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-api/src/opentelemetry/configuration/__init__.py b/opentelemetry-api/src/opentelemetry/configuration/__init__.py index 4809341502..0bd2bce8d6 100644 --- a/opentelemetry-api/src/opentelemetry/configuration/__init__.py +++ b/opentelemetry-api/src/opentelemetry/configuration/__init__.py @@ -12,9 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# FIXME find a better way to avoid all those "Expression has type "Any"" errors -# type: ignore - """ Simple configuration manager @@ -95,17 +92,23 @@ from os import environ from re import fullmatch +from typing import ClassVar, Dict, Optional, TypeVar, Union +ConfigValue = Union[str, bool, int, float] +_T = TypeVar("_T", ConfigValue, Optional[ConfigValue]) -class Configuration: - _instance = None - __slots__ = [] +class Configuration: + _instance = None # type: ClassVar[Optional[Configuration]] + _config_map = {} # type: ClassVar[Dict[str, ConfigValue]] def __new__(cls) -> "Configuration": - if Configuration._instance is None: + if cls._instance is not None: + instance = cls._instance + else: - for key, value in environ.items(): + instance = super().__new__(cls) + for key, value_str in environ.items(): match = fullmatch( r"OPENTELEMETRY_PYTHON_([A-Za-z_][\w_]*)", key @@ -114,45 +117,47 @@ def __new__(cls) -> "Configuration": if match is not None: key = match.group(1) + value = value_str # type: ConfigValue - if value == "True": + if value_str == "True": value = True - elif value == "False": + elif value_str == "False": value = False else: try: - value = int(value) + value = int(value_str) except ValueError: pass try: - value = float(value) + value = float(value_str) except ValueError: pass - setattr(Configuration, "_{}".format(key), value) - setattr( - Configuration, - key, - property( - fget=lambda cls, key=key: getattr( - cls, "_{}".format(key) - ) - ), - ) + instance._config_map[key] = value - Configuration.__slots__.append(key) + cls._instance = instance - Configuration.__slots__ = tuple(Configuration.__slots__) + return instance - Configuration._instance = object.__new__(cls) + def __getattr__(self, name: str) -> Optional[ConfigValue]: + return self._config_map.get(name) - return cls._instance + def __setattr__(self, key: str, val: ConfigValue) -> None: + if key == "_config_map": + super().__setattr__(key, val) + else: + raise AttributeError(key) - def __getattr__(self, name): - return None + def get(self, name: str, default: _T) -> _T: + """Use this typed method for dynamic access instead of `getattr` + + :rtype: str or bool or int or float or None + """ + val = self._config_map.get(name, default) + return val @classmethod - def _reset(cls): + def _reset(cls) -> None: """ This method "resets" the global configuration attributes @@ -160,10 +165,6 @@ def _reset(cls): only. """ - for slot in cls.__slots__: - if slot in cls.__dict__.keys(): - delattr(cls, slot) - delattr(cls, "_{}".format(slot)) - - cls.__slots__ = [] - cls._instance = None + if cls._instance: + cls._instance._config_map.clear() # pylint: disable=protected-access + cls._instance = None diff --git a/opentelemetry-api/src/opentelemetry/metrics/__init__.py b/opentelemetry-api/src/opentelemetry/metrics/__init__.py index 16ac4c096f..aa2988bce4 100644 --- a/opentelemetry-api/src/opentelemetry/metrics/__init__.py +++ b/opentelemetry-api/src/opentelemetry/metrics/__init__.py @@ -31,7 +31,7 @@ from logging import getLogger from typing import Callable, Dict, Optional, Sequence, Tuple, Type, TypeVar -from opentelemetry.util import _load_provider +from opentelemetry.util import _load_meter_provider logger = getLogger(__name__) ValueT = TypeVar("ValueT", int, float) @@ -66,7 +66,17 @@ def add(self, value: ValueT) -> None: """Increases the value of the bound counter by ``value``. Args: - value: The value to add to the bound counter. + value: The value to add to the bound counter. Must be positive. + """ + + +class BoundUpDownCounter: + def add(self, value: ValueT) -> None: + """Increases the value of the bound counter by ``value``. + + Args: + value: The value to add to the bound counter. Can be positive or + negative. """ @@ -137,7 +147,28 @@ def add(self, value: ValueT, labels: Dict[str, str]) -> None: """Increases the value of the counter by ``value``. Args: - value: The value to add to the counter metric. + value: The value to add to the counter metric. Should be positive + or zero. For a Counter that can decrease, use + `UpDownCounter`. + labels: Labels to associate with the bound instrument. + """ + + +class UpDownCounter(Metric): + """A counter type metric that expresses the computation of a sum, + allowing negative increments.""" + + def bind(self, labels: Dict[str, str]) -> "BoundUpDownCounter": + """Gets a `BoundUpDownCounter`.""" + return BoundUpDownCounter() + + def add(self, value: ValueT, labels: Dict[str, str]) -> None: + """Increases the value of the counter by ``value``. + + Args: + value: The value to add to the counter metric. Can be positive or + negative. For a Counter that is never decreasing, use + `Counter`. labels: Labels to associate with the bound instrument. """ @@ -162,7 +193,6 @@ class Observer(abc.ABC): """An observer type metric instrument used to capture a current set of values. - Observer instruments are asynchronous, a callback is invoked with the observer instrument as argument allowing the user to capture multiple values per collection interval. @@ -190,6 +220,42 @@ def observe(self, value: ValueT, labels: Dict[str, str]) -> None: """ +class SumObserver(Observer): + """No-op implementation of ``SumObserver``.""" + + def observe(self, value: ValueT, labels: Dict[str, str]) -> None: + """Captures ``value`` to the sumobserver. + + Args: + value: The value to capture to this sumobserver metric. + labels: Labels associated to ``value``. + """ + + +class UpDownSumObserver(Observer): + """No-op implementation of ``UpDownSumObserver``.""" + + def observe(self, value: ValueT, labels: Dict[str, str]) -> None: + """Captures ``value`` to the updownsumobserver. + + Args: + value: The value to capture to this updownsumobserver metric. + labels: Labels associated to ``value``. + """ + + +class ValueObserver(Observer): + """No-op implementation of ``ValueObserver``.""" + + def observe(self, value: ValueT, labels: Dict[str, str]) -> None: + """Captures ``value`` to the valueobserver. + + Args: + value: The value to capture to this valueobserver metric. + labels: Labels associated to ``value``. + """ + + class MeterProvider(abc.ABC): @abc.abstractmethod def get_meter( @@ -232,7 +298,11 @@ def get_meter( return DefaultMeter() -MetricT = TypeVar("MetricT", Counter, ValueRecorder, Observer) +MetricT = TypeVar("MetricT", Counter, ValueRecorder) +InstrumentT = TypeVar( + "InstrumentT", Counter, UpDownCounter, Observer, ValueRecorder +) +ObserverT = TypeVar("ObserverT", bound=Observer) ObserverCallbackT = Callable[[Observer], None] @@ -297,6 +367,7 @@ def register_observer( description: str, unit: str, value_type: Type[ValueT], + observer_type: Type[ObserverT], label_keys: Sequence[str] = (), enabled: bool = True, ) -> "Observer": @@ -310,6 +381,7 @@ def register_observer( unit: Unit of the metric values following the UCUM convention (https://unitsofmeasure.org/ucum.html). value_type: The type of values being recorded by the metric. + observer_type: The type of observer being registered. label_keys: The keys for the labels with dynamic values. enabled: Whether to report the metric by default. Returns: A new ``Observer`` metric instrument. @@ -354,6 +426,7 @@ def register_observer( description: str, unit: str, value_type: Type[ValueT], + observer_type: Type[ObserverT], label_keys: Sequence[str] = (), enabled: bool = True, ) -> "Observer": @@ -395,6 +468,6 @@ def get_meter_provider() -> MeterProvider: global _METER_PROVIDER # pylint: disable=global-statement if _METER_PROVIDER is None: - _METER_PROVIDER = _load_provider("meter_provider") + _METER_PROVIDER = _load_meter_provider("meter_provider") return _METER_PROVIDER diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py index 13aaf2c6a4..fa0bc376e7 100644 --- a/opentelemetry-api/src/opentelemetry/trace/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py @@ -70,6 +70,36 @@ `set_tracer_provider`. """ +__all__ = [ + "DEFAULT_TRACE_OPTIONS", + "DEFAULT_TRACE_STATE", + "INVALID_SPAN", + "INVALID_SPAN_CONTEXT", + "INVALID_SPAN_ID", + "INVALID_TRACE_ID", + "DefaultSpan", + "DefaultTracer", + "DefaultTracerProvider", + "LazyLink", + "Link", + "LinkBase", + "ParentSpan", + "Span", + "SpanContext", + "SpanKind", + "TraceFlags", + "TraceState", + "TracerProvider", + "Tracer", + "format_span_id", + "format_trace_id", + "get_current_span", + "get_tracer", + "get_tracer_provider", + "set_tracer_provider", + "set_span_in_context", +] + import abc import enum import types as python_types @@ -77,8 +107,28 @@ from contextlib import contextmanager from logging import getLogger +from opentelemetry.configuration import Configuration +from opentelemetry.trace.propagation import ( + get_current_span, + set_span_in_context, +) +from opentelemetry.trace.span import ( + DEFAULT_TRACE_OPTIONS, + DEFAULT_TRACE_STATE, + INVALID_SPAN, + INVALID_SPAN_CONTEXT, + INVALID_SPAN_ID, + INVALID_TRACE_ID, + DefaultSpan, + Span, + SpanContext, + TraceFlags, + TraceState, + format_span_id, + format_trace_id, +) from opentelemetry.trace.status import Status -from opentelemetry.util import _load_provider, types +from opentelemetry.util import _load_trace_provider, types logger = getLogger(__name__) @@ -170,275 +220,6 @@ class SpanKind(enum.Enum): CONSUMER = 4 -class Span(abc.ABC): - """A span represents a single operation within a trace.""" - - @abc.abstractmethod - def end(self, end_time: typing.Optional[int] = None) -> None: - """Sets the current time as the span's end time. - - The span's end time is the wall time at which the operation finished. - - Only the first call to `end` should modify the span, and - implementations are free to ignore or raise on further calls. - """ - - @abc.abstractmethod - def get_context(self) -> "SpanContext": - """Gets the span's SpanContext. - - Get an immutable, serializable identifier for this span that can be - used to create new child spans. - - Returns: - A :class:`.SpanContext` with a copy of this span's immutable state. - """ - - @abc.abstractmethod - def set_attribute(self, key: str, value: types.AttributeValue) -> None: - """Sets an Attribute. - - Sets a single Attribute with the key and value passed as arguments. - """ - - @abc.abstractmethod - def add_event( - self, - name: str, - attributes: types.Attributes = None, - timestamp: typing.Optional[int] = None, - ) -> None: - """Adds an `Event`. - - Adds a single `Event` with the name and, optionally, a timestamp and - attributes passed as arguments. Implementations should generate a - timestamp if the `timestamp` argument is omitted. - """ - - @abc.abstractmethod - def add_lazy_event( - self, - name: str, - event_formatter: types.AttributesFormatter, - timestamp: typing.Optional[int] = None, - ) -> None: - """Adds an `Event`. - - Adds a single `Event` with the name, an event formatter that calculates - the attributes lazily and, optionally, a timestamp. Implementations - should generate a timestamp if the `timestamp` argument is omitted. - """ - - @abc.abstractmethod - def update_name(self, name: str) -> None: - """Updates the `Span` name. - - This will override the name provided via :func:`Tracer.start_span`. - - Upon this update, any sampling behavior based on Span name will depend - on the implementation. - """ - - @abc.abstractmethod - def is_recording_events(self) -> bool: - """Returns whether this span will be recorded. - - Returns true if this Span is active and recording information like - events with the add_event operation and attributes using set_attribute. - """ - - @abc.abstractmethod - def set_status(self, status: Status) -> None: - """Sets the Status of the Span. If used, this will override the default - Span status, which is OK. - """ - - def __enter__(self) -> "Span": - """Invoked when `Span` is used as a context manager. - - Returns the `Span` itself. - """ - return self - - def __exit__( - self, - exc_type: typing.Optional[typing.Type[BaseException]], - exc_val: typing.Optional[BaseException], - exc_tb: typing.Optional[python_types.TracebackType], - ) -> None: - """Ends context manager and calls `end` on the `Span`.""" - - self.end() - - -class TraceFlags(int): - """A bitmask that represents options specific to the trace. - - The only supported option is the "sampled" flag (``0x01``). If set, this - flag indicates that the trace may have been sampled upstream. - - See the `W3C Trace Context - Traceparent`_ spec for details. - - .. _W3C Trace Context - Traceparent: - https://www.w3.org/TR/trace-context/#trace-flags - """ - - DEFAULT = 0x00 - SAMPLED = 0x01 - - @classmethod - def get_default(cls) -> "TraceFlags": - return cls(cls.DEFAULT) - - @property - def sampled(self) -> bool: - return bool(self & TraceFlags.SAMPLED) - - -DEFAULT_TRACE_OPTIONS = TraceFlags.get_default() - - -class TraceState(typing.Dict[str, str]): - """A list of key-value pairs representing vendor-specific trace info. - - Keys and values are strings of up to 256 printable US-ASCII characters. - Implementations should conform to the `W3C Trace Context - Tracestate`_ - spec, which describes additional restrictions on valid field values. - - .. _W3C Trace Context - Tracestate: - https://www.w3.org/TR/trace-context/#tracestate-field - """ - - @classmethod - def get_default(cls) -> "TraceState": - return cls() - - -DEFAULT_TRACE_STATE = TraceState.get_default() - - -def format_trace_id(trace_id: int) -> str: - return "0x{:032x}".format(trace_id) - - -def format_span_id(span_id: int) -> str: - return "0x{:016x}".format(span_id) - - -class SpanContext: - """The state of a Span to propagate between processes. - - This class includes the immutable attributes of a :class:`.Span` that must - be propagated to a span's children and across process boundaries. - - Args: - trace_id: The ID of the trace that this span belongs to. - span_id: This span's ID. - trace_flags: Trace options to propagate. - trace_state: Tracing-system-specific info to propagate. - is_remote: True if propagated from a remote parent. - """ - - def __init__( - self, - trace_id: int, - span_id: int, - is_remote: bool, - trace_flags: "TraceFlags" = DEFAULT_TRACE_OPTIONS, - trace_state: "TraceState" = DEFAULT_TRACE_STATE, - ) -> None: - if trace_flags is None: - trace_flags = DEFAULT_TRACE_OPTIONS - if trace_state is None: - trace_state = DEFAULT_TRACE_STATE - self.trace_id = trace_id - self.span_id = span_id - self.trace_flags = trace_flags - self.trace_state = trace_state - self.is_remote = is_remote - - def __repr__(self) -> str: - return ( - "{}(trace_id={}, span_id={}, trace_state={!r}, is_remote={})" - ).format( - type(self).__name__, - format_trace_id(self.trace_id), - format_span_id(self.span_id), - self.trace_state, - self.is_remote, - ) - - def is_valid(self) -> bool: - """Get whether this `SpanContext` is valid. - - A `SpanContext` is said to be invalid if its trace ID or span ID is - invalid (i.e. ``0``). - - Returns: - True if the `SpanContext` is valid, false otherwise. - """ - return ( - self.trace_id != INVALID_TRACE_ID - and self.span_id != INVALID_SPAN_ID - ) - - -class DefaultSpan(Span): - """The default Span that is used when no Span implementation is available. - - All operations are no-op except context propagation. - """ - - def __init__(self, context: "SpanContext") -> None: - self._context = context - - def get_context(self) -> "SpanContext": - return self._context - - def is_recording_events(self) -> bool: - return False - - def end(self, end_time: typing.Optional[int] = None) -> None: - pass - - def set_attribute(self, key: str, value: types.AttributeValue) -> None: - pass - - def add_event( - self, - name: str, - attributes: types.Attributes = None, - timestamp: typing.Optional[int] = None, - ) -> None: - pass - - def add_lazy_event( - self, - name: str, - event_formatter: types.AttributesFormatter, - timestamp: typing.Optional[int] = None, - ) -> None: - pass - - def update_name(self, name: str) -> None: - pass - - def set_status(self, status: Status) -> None: - pass - - -INVALID_SPAN_ID = 0x0000000000000000 -INVALID_TRACE_ID = 0x00000000000000000000000000000000 -INVALID_SPAN_CONTEXT = SpanContext( - trace_id=INVALID_TRACE_ID, - span_id=INVALID_SPAN_ID, - is_remote=False, - trace_flags=DEFAULT_TRACE_OPTIONS, - trace_state=DEFAULT_TRACE_STATE, -) -INVALID_SPAN = DefaultSpan(INVALID_SPAN_CONTEXT) - - class TracerProvider(abc.ABC): @abc.abstractmethod def get_tracer( @@ -495,18 +276,6 @@ class Tracer(abc.ABC): # This is the default behavior when creating spans. CURRENT_SPAN = DefaultSpan(INVALID_SPAN_CONTEXT) - @abc.abstractmethod - def get_current_span(self) -> "Span": - """Gets the currently active span from the context. - - If there is no current span, return a placeholder span with an invalid - context. - - Returns: - The currently active :class:`.Span`, or a placeholder span with an - invalid :class:`.SpanContext`. - """ - @abc.abstractmethod def start_span( self, @@ -524,7 +293,7 @@ def start_span( span in this tracer's context. By default the current span will be used as parent, but an explicit - parent can also be specified, either a `Span` or a `SpanContext`. If + parent can also be specified, either a `Span` or a `opentelemetry.trace.SpanContext`. If the specified value is `None`, the created span will be a root span. The span can be used as context manager. On exiting, the span will be @@ -532,7 +301,7 @@ def start_span( Example:: - # tracer.get_current_span() will be used as the implicit parent. + # trace.get_current_span() will be used as the implicit parent. # If none is found, the created span will be a root instance. with tracer.start_span("one") as child: child.add_event("child's event") @@ -578,11 +347,11 @@ def start_as_current_span( with tracer.start_as_current_span("one") as parent: parent.add_event("parent's event") - with tracer.start_as_current_span("two") as child: + with trace.start_as_current_span("two") as child: child.add_event("child's event") - tracer.get_current_span() # returns child - tracer.get_current_span() # returns parent - tracer.get_current_span() # returns previously active span + trace.get_current_span() # returns child + trace.get_current_span() # returns parent + trace.get_current_span() # returns previously active span This is a convenience method for creating spans attached to the tracer's context. Applications that need more control over the span @@ -636,10 +405,6 @@ class DefaultTracer(Tracer): All operations are no-op. """ - def get_current_span(self) -> "Span": - # pylint: disable=no-self-use - return INVALID_SPAN - def start_span( self, name: str, @@ -706,6 +471,6 @@ def get_tracer_provider() -> TracerProvider: global _TRACER_PROVIDER # pylint: disable=global-statement if _TRACER_PROVIDER is None: - _TRACER_PROVIDER = _load_provider("tracer_provider") + _TRACER_PROVIDER = _load_trace_provider("tracer_provider") return _TRACER_PROVIDER diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py index f17350c745..45a07c920d 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py @@ -13,22 +13,40 @@ # limitations under the License. from typing import Optional -from opentelemetry import trace as trace_api from opentelemetry.context import get_value, set_value from opentelemetry.context.context import Context +from opentelemetry.trace.span import INVALID_SPAN, Span SPAN_KEY = "current-span" def set_span_in_context( - span: trace_api.Span, context: Optional[Context] = None + span: Span, context: Optional[Context] = None ) -> Context: + """Set the span in the given context. + + Args: + span: The Span to set. + context: a Context object. if one is not passed, the + default current context is used instead. + """ ctx = set_value(SPAN_KEY, span, context=context) return ctx -def get_span_from_context(context: Optional[Context] = None) -> trace_api.Span: +def get_current_span(context: Optional[Context] = None) -> Optional[Span]: + """Retrieve the current span. + + Args: + context: A Context object. If one is not passed, the + default current context is used instead. + + Returns: + The Span set in the context if it exists. None otherwise. + """ span = get_value(SPAN_KEY, context=context) - if not isinstance(span, trace_api.Span): - return trace_api.INVALID_SPAN + if span is None: + return None + if not isinstance(span, Span): + return INVALID_SPAN return span diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py index 732ce96c66..fa2fae8703 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py @@ -17,11 +17,7 @@ import opentelemetry.trace as trace from opentelemetry.context.context import Context -from opentelemetry.trace.propagation import ( - get_span_from_context, - httptextformat, - set_span_in_context, -) +from opentelemetry.trace.propagation import httptextformat # Keys and values are strings of up to 256 printable US-ASCII characters. # Implementations should conform to the `W3C Trace Context - Tracestate`_ @@ -77,11 +73,11 @@ def extract( header = get_from_carrier(carrier, self._TRACEPARENT_HEADER_NAME) if not header: - return set_span_in_context(trace.INVALID_SPAN, context) + return trace.set_span_in_context(trace.INVALID_SPAN, context) match = re.search(self._TRACEPARENT_HEADER_FORMAT_RE, header[0]) if not match: - return set_span_in_context(trace.INVALID_SPAN, context) + return trace.set_span_in_context(trace.INVALID_SPAN, context) version = match.group(1) trace_id = match.group(2) @@ -89,13 +85,13 @@ def extract( trace_flags = match.group(4) if trace_id == "0" * 32 or span_id == "0" * 16: - return set_span_in_context(trace.INVALID_SPAN, context) + return trace.set_span_in_context(trace.INVALID_SPAN, context) if version == "00": if match.group(5): - return set_span_in_context(trace.INVALID_SPAN, context) + return trace.set_span_in_context(trace.INVALID_SPAN, context) if version == "ff": - return set_span_in_context(trace.INVALID_SPAN, context) + return trace.set_span_in_context(trace.INVALID_SPAN, context) tracestate_headers = get_from_carrier( carrier, self._TRACESTATE_HEADER_NAME @@ -109,7 +105,9 @@ def extract( trace_flags=trace.TraceFlags(trace_flags), trace_state=tracestate, ) - return set_span_in_context(trace.DefaultSpan(span_context), context) + return trace.set_span_in_context( + trace.DefaultSpan(span_context), context + ) def inject( self, @@ -121,8 +119,10 @@ def inject( See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.inject` """ - span_context = get_span_from_context(context).get_context() - + span = trace.get_current_span(context) + if span is None: + return + span_context = span.get_context() if span_context == trace.INVALID_SPAN_CONTEXT: return traceparent_string = "00-{:032x}-{:016x}-{:02x}".format( diff --git a/opentelemetry-api/src/opentelemetry/trace/span.py b/opentelemetry-api/src/opentelemetry/trace/span.py new file mode 100644 index 0000000000..b20979397d --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/trace/span.py @@ -0,0 +1,281 @@ +import abc +import types as python_types +import typing + +from opentelemetry.trace.status import Status +from opentelemetry.util import types + + +class Span(abc.ABC): + """A span represents a single operation within a trace.""" + + @abc.abstractmethod + def end(self, end_time: typing.Optional[int] = None) -> None: + """Sets the current time as the span's end time. + + The span's end time is the wall time at which the operation finished. + + Only the first call to `end` should modify the span, and + implementations are free to ignore or raise on further calls. + """ + + @abc.abstractmethod + def get_context(self) -> "SpanContext": + """Gets the span's SpanContext. + + Get an immutable, serializable identifier for this span that can be + used to create new child spans. + + Returns: + A :class:`opentelemetry.trace.SpanContext` with a copy of this span's immutable state. + """ + + @abc.abstractmethod + def set_attribute(self, key: str, value: types.AttributeValue) -> None: + """Sets an Attribute. + + Sets a single Attribute with the key and value passed as arguments. + """ + + @abc.abstractmethod + def add_event( + self, + name: str, + attributes: types.Attributes = None, + timestamp: typing.Optional[int] = None, + ) -> None: + """Adds an `Event`. + + Adds a single `Event` with the name and, optionally, a timestamp and + attributes passed as arguments. Implementations should generate a + timestamp if the `timestamp` argument is omitted. + """ + + @abc.abstractmethod + def add_lazy_event( + self, + name: str, + event_formatter: types.AttributesFormatter, + timestamp: typing.Optional[int] = None, + ) -> None: + """Adds an `Event`. + Adds a single `Event` with the name, an event formatter that calculates + the attributes lazily and, optionally, a timestamp. Implementations + should generate a timestamp if the `timestamp` argument is omitted. + """ + + @abc.abstractmethod + def update_name(self, name: str) -> None: + """Updates the `Span` name. + + This will override the name provided via :func:`opentelemetry.trace.Tracer.start_span`. + + Upon this update, any sampling behavior based on Span name will depend + on the implementation. + """ + + @abc.abstractmethod + def is_recording_events(self) -> bool: + """Returns whether this span will be recorded. + + Returns true if this Span is active and recording information like + events with the add_event operation and attributes using set_attribute. + """ + + @abc.abstractmethod + def set_status(self, status: Status) -> None: + """Sets the Status of the Span. If used, this will override the default + Span status, which is OK. + """ + + @abc.abstractmethod + def record_error(self, err: Exception) -> None: + """Records an error as a span event.""" + + def __enter__(self) -> "Span": + """Invoked when `Span` is used as a context manager. + + Returns the `Span` itself. + """ + return self + + def __exit__( + self, + exc_type: typing.Optional[typing.Type[BaseException]], + exc_val: typing.Optional[BaseException], + exc_tb: typing.Optional[python_types.TracebackType], + ) -> None: + """Ends context manager and calls `end` on the `Span`.""" + + self.end() + + +class TraceFlags(int): + """A bitmask that represents options specific to the trace. + + The only supported option is the "sampled" flag (``0x01``). If set, this + flag indicates that the trace may have been sampled upstream. + + See the `W3C Trace Context - Traceparent`_ spec for details. + + .. _W3C Trace Context - Traceparent: + https://www.w3.org/TR/trace-context/#trace-flags + """ + + DEFAULT = 0x00 + SAMPLED = 0x01 + + @classmethod + def get_default(cls) -> "TraceFlags": + return cls(cls.DEFAULT) + + @property + def sampled(self) -> bool: + return bool(self & TraceFlags.SAMPLED) + + +DEFAULT_TRACE_OPTIONS = TraceFlags.get_default() + + +class TraceState(typing.Dict[str, str]): + """A list of key-value pairs representing vendor-specific trace info. + + Keys and values are strings of up to 256 printable US-ASCII characters. + Implementations should conform to the `W3C Trace Context - Tracestate`_ + spec, which describes additional restrictions on valid field values. + + .. _W3C Trace Context - Tracestate: + https://www.w3.org/TR/trace-context/#tracestate-field + """ + + @classmethod + def get_default(cls) -> "TraceState": + return cls() + + +DEFAULT_TRACE_STATE = TraceState.get_default() + + +class SpanContext: + """The state of a Span to propagate between processes. + + This class includes the immutable attributes of a :class:`.Span` that must + be propagated to a span's children and across process boundaries. + + Args: + trace_id: The ID of the trace that this span belongs to. + span_id: This span's ID. + trace_flags: Trace options to propagate. + trace_state: Tracing-system-specific info to propagate. + is_remote: True if propagated from a remote parent. + """ + + def __init__( + self, + trace_id: int, + span_id: int, + is_remote: bool, + trace_flags: "TraceFlags" = DEFAULT_TRACE_OPTIONS, + trace_state: "TraceState" = DEFAULT_TRACE_STATE, + ) -> None: + if trace_flags is None: + trace_flags = DEFAULT_TRACE_OPTIONS + if trace_state is None: + trace_state = DEFAULT_TRACE_STATE + self.trace_id = trace_id + self.span_id = span_id + self.trace_flags = trace_flags + self.trace_state = trace_state + self.is_remote = is_remote + + def __repr__(self) -> str: + return ( + "{}(trace_id={}, span_id={}, trace_state={!r}, is_remote={})" + ).format( + type(self).__name__, + format_trace_id(self.trace_id), + format_span_id(self.span_id), + self.trace_state, + self.is_remote, + ) + + def is_valid(self) -> bool: + """Get whether this `SpanContext` is valid. + + A `SpanContext` is said to be invalid if its trace ID or span ID is + invalid (i.e. ``0``). + + Returns: + True if the `SpanContext` is valid, false otherwise. + """ + return ( + self.trace_id != INVALID_TRACE_ID + and self.span_id != INVALID_SPAN_ID + ) + + +class DefaultSpan(Span): + """The default Span that is used when no Span implementation is available. + + All operations are no-op except context propagation. + """ + + def __init__(self, context: "SpanContext") -> None: + self._context = context + + def get_context(self) -> "SpanContext": + return self._context + + def is_recording_events(self) -> bool: + return False + + def end(self, end_time: typing.Optional[int] = None) -> None: + pass + + def set_attribute(self, key: str, value: types.AttributeValue) -> None: + pass + + def add_event( + self, + name: str, + attributes: types.Attributes = None, + timestamp: typing.Optional[int] = None, + ) -> None: + pass + + def add_lazy_event( + self, + name: str, + event_formatter: types.AttributesFormatter, + timestamp: typing.Optional[int] = None, + ) -> None: + pass + + def update_name(self, name: str) -> None: + pass + + def set_status(self, status: Status) -> None: + pass + + def record_error(self, err: Exception) -> None: + pass + + +INVALID_SPAN_ID = 0x0000000000000000 +INVALID_TRACE_ID = 0x00000000000000000000000000000000 +INVALID_SPAN_CONTEXT = SpanContext( + trace_id=INVALID_TRACE_ID, + span_id=INVALID_SPAN_ID, + is_remote=False, + trace_flags=DEFAULT_TRACE_OPTIONS, + trace_state=DEFAULT_TRACE_STATE, +) +INVALID_SPAN = DefaultSpan(INVALID_SPAN_CONTEXT) + + +def format_trace_id(trace_id: int) -> str: + return "0x{:032x}".format(trace_id) + + +def format_span_id(span_id: int) -> str: + return "0x{:016x}".format(span_id) diff --git a/opentelemetry-api/src/opentelemetry/util/__init__.py b/opentelemetry-api/src/opentelemetry/util/__init__.py index bab42b0bde..ed1268fcb6 100644 --- a/opentelemetry-api/src/opentelemetry/util/__init__.py +++ b/opentelemetry-api/src/opentelemetry/util/__init__.py @@ -14,11 +14,17 @@ import re import time from logging import getLogger -from typing import Sequence, Union +from typing import TYPE_CHECKING, Sequence, Union, cast from pkg_resources import iter_entry_points -from opentelemetry.configuration import Configuration # type: ignore +from opentelemetry.configuration import Configuration + +if TYPE_CHECKING: + from opentelemetry.trace import TracerProvider + from opentelemetry.metrics import MeterProvider + +Provider = Union["TracerProvider", "MeterProvider"] logger = getLogger(__name__) @@ -34,25 +40,33 @@ def time_ns() -> int: return int(time.time() * 1e9) -def _load_provider( - provider: str, -) -> Union["TracerProvider", "MeterProvider"]: # type: ignore +def _load_provider(provider: str) -> Provider: try: - return next( # type: ignore + entry_point = next( iter_entry_points( "opentelemetry_{}".format(provider), - name=getattr( - Configuration(), # type: ignore - provider, - "default_{}".format(provider), + name=cast( + str, + Configuration().get( + provider, "default_{}".format(provider), + ), ), ) - ).load()() + ) + return cast(Provider, entry_point.load()(),) except Exception: # pylint: disable=broad-except logger.error("Failed to load configured provider %s", provider) raise +def _load_meter_provider(provider: str) -> "MeterProvider": + return cast("MeterProvider", _load_provider(provider)) + + +def _load_trace_provider(provider: str) -> "TracerProvider": + return cast("TracerProvider", _load_provider(provider)) + + # Pattern for matching up until the first '/' after the 'https://' part. _URL_PATTERN = r"(https?|ftp)://.*?/" diff --git a/opentelemetry-api/src/opentelemetry/version.py b/opentelemetry-api/src/opentelemetry/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/opentelemetry-api/src/opentelemetry/version.py +++ b/opentelemetry-api/src/opentelemetry/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/opentelemetry-api/tests/configuration/test_configuration.py b/opentelemetry-api/tests/configuration/test_configuration.py index a817a1bf43..32b62e619d 100644 --- a/opentelemetry-api/tests/configuration/test_configuration.py +++ b/opentelemetry-api/tests/configuration/test_configuration.py @@ -16,16 +16,16 @@ from unittest import TestCase from unittest.mock import patch -from opentelemetry.configuration import Configuration # type: ignore +from opentelemetry.configuration import Configuration class TestConfiguration(TestCase): - def tearDown(self): + def tearDown(self) -> None: # This call resets the attributes of the Configuration class so that # each test is executed in the same conditions. Configuration._reset() - def test_singleton(self): + def test_singleton(self) -> None: self.assertIsInstance(Configuration(), Configuration) self.assertIs(Configuration(), Configuration()) @@ -39,7 +39,7 @@ def test_singleton(self): "OPENTELEMETRY_PTHON_TRACEX_PROVIDER": "tracex_provider", }, ) - def test_environment_variables(self): # type: ignore + def test_environment_variables(self): self.assertEqual( Configuration().METER_PROVIDER, "meter_provider" ) # pylint: disable=no-member @@ -62,16 +62,21 @@ def test_property(self): with self.assertRaises(AttributeError): Configuration().TRACER_PROVIDER = "new_tracer_provider" - def test_slots(self): + def test_slots(self) -> None: with self.assertRaises(AttributeError): Configuration().XYZ = "xyz" # pylint: disable=assigning-non-slot - def test_getattr(self): + def test_getattr(self) -> None: + # literal access self.assertIsNone(Configuration().XYZ) - def test_reset(self): + # dynamic access + self.assertIsNone(getattr(Configuration(), "XYZ")) + self.assertIsNone(Configuration().get("XYZ", None)) + + def test_reset(self) -> None: environ_patcher = patch.dict( - "os.environ", # type: ignore + "os.environ", {"OPENTELEMETRY_PYTHON_TRACER_PROVIDER": "tracer_provider"}, ) @@ -96,7 +101,7 @@ def test_reset(self): "OPENTELEMETRY_PYTHON_FALSE": "False", }, ) - def test_boolean(self): + def test_boolean(self) -> None: self.assertIsInstance( Configuration().TRUE, bool ) # pylint: disable=no-member @@ -114,7 +119,7 @@ def test_boolean(self): "OPENTELEMETRY_PYTHON_NON_INTEGER": "-12z3", }, ) - def test_integer(self): + def test_integer(self) -> None: self.assertEqual( Configuration().POSITIVE_INTEGER, 123 ) # pylint: disable=no-member @@ -133,7 +138,7 @@ def test_integer(self): "OPENTELEMETRY_PYTHON_NON_FLOAT": "-12z3.123", }, ) - def test_float(self): + def test_float(self) -> None: self.assertEqual( Configuration().POSITIVE_FLOAT, 123.123 ) # pylint: disable=no-member diff --git a/opentelemetry-api/tests/metrics/test_metrics.py b/opentelemetry-api/tests/metrics/test_metrics.py index 897c7492e4..aeec4b4ff4 100644 --- a/opentelemetry-api/tests/metrics/test_metrics.py +++ b/opentelemetry-api/tests/metrics/test_metrics.py @@ -35,6 +35,16 @@ def test_counter_add(self): counter = metrics.Counter() counter.add(1, {}) + def test_updowncounter(self): + counter = metrics.UpDownCounter() + bound_counter = counter.bind({}) + self.assertIsInstance(bound_counter, metrics.BoundUpDownCounter) + + def test_updowncounter_add(self): + counter = metrics.Counter() + counter.add(1, {}) + counter.add(-1, {}) + def test_valuerecorder(self): valuerecorder = metrics.ValueRecorder() bound_valuerecorder = valuerecorder.bind({}) @@ -56,6 +66,18 @@ def test_bound_valuerecorder(self): bound_valuerecorder = metrics.BoundValueRecorder() bound_valuerecorder.record(1) - def test_observer(self): + def test_default_observer(self): observer = metrics.DefaultObserver() observer.observe(1, {}) + + def test_sum_observer(self): + observer = metrics.SumObserver() + observer.observe(1, {}) + + def test_updown_sum_observer(self): + observer = metrics.UpDownSumObserver() + observer.observe(1, {}) + + def test_value_observer(self): + observer = metrics.ValueObserver() + observer.observe(1, {}) diff --git a/opentelemetry-api/tests/propagators/test_global_httptextformat.py b/opentelemetry-api/tests/propagators/test_global_httptextformat.py index cac24f30a0..a7e9430223 100644 --- a/opentelemetry-api/tests/propagators/test_global_httptextformat.py +++ b/opentelemetry-api/tests/propagators/test_global_httptextformat.py @@ -17,10 +17,7 @@ from opentelemetry import correlationcontext, trace from opentelemetry.propagators import extract, inject -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, -) +from opentelemetry.trace import get_current_span, set_span_in_context def get_as_list( @@ -52,7 +49,7 @@ def test_propagation(self): correlations = correlationcontext.get_correlations(context=ctx) expected = {"key1": "val1", "key2": "val2"} self.assertEqual(correlations, expected) - span_context = get_span_from_context(context=ctx).get_context() + span_context = get_current_span(context=ctx).get_context() self.assertEqual(span_context.trace_id, self.TRACE_ID) self.assertEqual(span_context.span_id, self.SPAN_ID) diff --git a/opentelemetry-api/tests/test_implementation.py b/opentelemetry-api/tests/test_implementation.py index 735ac4a683..d0f9404a91 100644 --- a/opentelemetry-api/tests/test_implementation.py +++ b/opentelemetry-api/tests/test_implementation.py @@ -83,7 +83,9 @@ def test_create_metric(self): def test_register_observer(self): meter = metrics.DefaultMeter() callback = mock.Mock() - observer = meter.register_observer(callback, "", "", "", int, (), True) + observer = meter.register_observer( + callback, "", "", "", int, metrics.ValueObserver + ) self.assertIsInstance(observer, metrics.DefaultObserver) def test_unregister_observer(self): diff --git a/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py index 11a8ecd56e..5adc180d9f 100644 --- a/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py @@ -16,11 +16,7 @@ import unittest from opentelemetry import trace -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, - tracecontexthttptextformat, -) +from opentelemetry.trace.propagation import tracecontexthttptextformat FORMAT = tracecontexthttptextformat.TraceContextHTTPTextFormat() @@ -46,7 +42,7 @@ def test_no_traceparent_header(self): trace-id and parent-id that represents the current request. """ output = {} # type:typing.Dict[str, typing.List[str]] - span = get_span_from_context(FORMAT.extract(get_as_list, output)) + span = trace.get_current_span(FORMAT.extract(get_as_list, output)) self.assertIsInstance(span.get_context(), trace.SpanContext) def test_headers_with_tracestate(self): @@ -58,7 +54,7 @@ def test_headers_with_tracestate(self): span_id=format(self.SPAN_ID, "016x"), ) tracestate_value = "foo=1,bar=2,baz=3" - span_context = get_span_from_context( + span_context = trace.get_current_span( FORMAT.extract( get_as_list, { @@ -76,7 +72,7 @@ def test_headers_with_tracestate(self): output = {} # type:typing.Dict[str, str] span = trace.DefaultSpan(span_context) - ctx = set_span_in_context(span) + ctx = trace.set_span_in_context(span) FORMAT.inject(dict.__setitem__, output, ctx) self.assertEqual(output["traceparent"], traceparent_value) for pair in ["foo=1", "bar=2", "baz=3"]: @@ -102,7 +98,7 @@ def test_invalid_trace_id(self): Note that the opposite is not true: failure to parse tracestate MUST NOT affect the parsing of traceparent. """ - span = get_span_from_context( + span = trace.get_current_span( FORMAT.extract( get_as_list, { @@ -133,7 +129,7 @@ def test_invalid_parent_id(self): Note that the opposite is not true: failure to parse tracestate MUST NOT affect the parsing of traceparent. """ - span = get_span_from_context( + span = trace.get_current_span( FORMAT.extract( get_as_list, { @@ -158,7 +154,7 @@ def test_no_send_empty_tracestate(self): span = trace.DefaultSpan( trace.SpanContext(self.TRACE_ID, self.SPAN_ID, is_remote=False) ) - ctx = set_span_in_context(span) + ctx = trace.set_span_in_context(span) FORMAT.inject(dict.__setitem__, output, ctx) self.assertTrue("traceparent" in output) self.assertFalse("tracestate" in output) @@ -171,7 +167,7 @@ def test_format_not_supported(self): If the version cannot be parsed, return an invalid trace header. """ - span = get_span_from_context( + span = trace.get_current_span( FORMAT.extract( get_as_list, { @@ -188,14 +184,14 @@ def test_format_not_supported(self): def test_propagate_invalid_context(self): """Do not propagate invalid trace context.""" output = {} # type:typing.Dict[str, str] - ctx = set_span_in_context(trace.INVALID_SPAN) + ctx = trace.set_span_in_context(trace.INVALID_SPAN) FORMAT.inject(dict.__setitem__, output, context=ctx) self.assertFalse("traceparent" in output) def test_tracestate_empty_header(self): """Test tracestate with an additional empty header (should be ignored) """ - span = get_span_from_context( + span = trace.get_current_span( FORMAT.extract( get_as_list, { @@ -211,7 +207,7 @@ def test_tracestate_empty_header(self): def test_tracestate_header_with_trailing_comma(self): """Do not propagate invalid trace context. """ - span = get_span_from_context( + span = trace.get_current_span( FORMAT.extract( get_as_list, { @@ -235,7 +231,7 @@ def test_tracestate_keys(self): "foo-_*/bar=bar4", ] ) - span = get_span_from_context( + span = trace.get_current_span( FORMAT.extract( get_as_list, { diff --git a/opentelemetry-api/tests/trace/test_globals.py b/opentelemetry-api/tests/trace/test_globals.py index 4f38f99ee8..2f0f88fb28 100644 --- a/opentelemetry-api/tests/trace/test_globals.py +++ b/opentelemetry-api/tests/trace/test_globals.py @@ -1,7 +1,7 @@ import unittest from unittest.mock import patch -from opentelemetry import trace +from opentelemetry import context, trace class TestGlobals(unittest.TestCase): @@ -16,7 +16,25 @@ def test_get_tracer(self): """trace.get_tracer should proxy to the global tracer provider.""" trace.get_tracer("foo", "var") self._mock_tracer_provider.get_tracer.assert_called_with("foo", "var") - mock_provider = unittest.mock.Mock() trace.get_tracer("foo", "var", mock_provider) mock_provider.get_tracer.assert_called_with("foo", "var") + + +class TestTracer(unittest.TestCase): + def setUp(self): + self.tracer = trace.DefaultTracer() + + def test_get_current_span(self): + """DefaultTracer's start_span will also + be retrievable via get_current_span + """ + self.assertIs(trace.get_current_span(), None) + span = trace.DefaultSpan(trace.INVALID_SPAN_CONTEXT) + ctx = trace.set_span_in_context(span) + token = context.attach(ctx) + try: + self.assertIs(trace.get_current_span(), span) + finally: + context.detach(token) + self.assertIs(trace.get_current_span(), None) diff --git a/opentelemetry-api/tests/trace/test_tracer.py b/opentelemetry-api/tests/trace/test_tracer.py index 4fe3d20f78..1eb1506230 100644 --- a/opentelemetry-api/tests/trace/test_tracer.py +++ b/opentelemetry-api/tests/trace/test_tracer.py @@ -21,10 +21,6 @@ class TestTracer(unittest.TestCase): def setUp(self): self.tracer = trace.DefaultTracer() - def test_get_current_span(self): - span = self.tracer.get_current_span() - self.assertIsInstance(span, trace.Span) - def test_start_span(self): with self.tracer.start_span("") as span: self.assertIsInstance(span, trace.Span) diff --git a/opentelemetry-auto-instrumentation/README.rst b/opentelemetry-auto-instrumentation/README.rst deleted file mode 100644 index b153072ae5..0000000000 --- a/opentelemetry-auto-instrumentation/README.rst +++ /dev/null @@ -1,19 +0,0 @@ -OpenTelemetry Auto Instrumentation -================================== - -|pypi| - -.. |pypi| image:: https://badge.fury.io/py/opentelemetry-auto-instrumentation.svg - :target: https://pypi.org/project/opentelemetry-auto-instrumentation/ - -Installation ------------- - -:: - - pip install opentelemetry-auto-instrumentation - -References ----------- - -* `OpenTelemetry Project `_ diff --git a/opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/__init__.py b/opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/__init__.py deleted file mode 100644 index 590bac8b3c..0000000000 --- a/opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright The OpenTelemetry Authors -# -# 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. - -""" - -This package provides a couple of commands that help automatically instruments a program: - -opentelemetry-auto-instrumentation ------------------------------------ - -:: - - opentelemetry-auto-instrumentation python program.py - -The code in ``program.py`` needs to use one of the packages for which there is -an OpenTelemetry integration. For a list of the available integrations please -check :doc:`here <../../index>`. - - -opentelemetry-bootstrap ------------------------- - -:: - - opentelemetry-bootstrap --action=install|requirements - -This commands inspects the active Python site-packages and figures out which -instrumentation packages the user might want to install. By default it prints out -a list of the suggested instrumentation packages which can be added to a requirements.txt -file. It also supports installing the suggested packages when run with :code:`--action=install` -flag. -""" diff --git a/opentelemetry-auto-instrumentation/CHANGELOG.md b/opentelemetry-instrumentation/CHANGELOG.md similarity index 75% rename from opentelemetry-auto-instrumentation/CHANGELOG.md rename to opentelemetry-instrumentation/CHANGELOG.md index d2521d157c..d6f04ae069 100644 --- a/opentelemetry-auto-instrumentation/CHANGELOG.md +++ b/opentelemetry-instrumentation/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +## 0.9b0 + +Released 2020-06-10 + +- Rename opentelemetry-auto-instrumentation to opentelemetry-instrumentation, + and console script `opentelemetry-auto-instrumentation` to `opentelemetry-instrument` + ## 0.8b0 Released 2020-05-27 diff --git a/opentelemetry-auto-instrumentation/MANIFEST.in b/opentelemetry-instrumentation/MANIFEST.in similarity index 100% rename from opentelemetry-auto-instrumentation/MANIFEST.in rename to opentelemetry-instrumentation/MANIFEST.in diff --git a/opentelemetry-instrumentation/README.rst b/opentelemetry-instrumentation/README.rst new file mode 100644 index 0000000000..6be744251b --- /dev/null +++ b/opentelemetry-instrumentation/README.rst @@ -0,0 +1,47 @@ +OpenTelemetry Instrumentation +============================= + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation.svg + :target: https://pypi.org/project/opentelemetry-instrumentation/ + +Installation +------------ + +:: + + pip install opentelemetry-instrumentation + + +This package provides a couple of commands that help automatically instruments a program: + +opentelemetry-instrument +------------------------ + +:: + + opentelemetry-instrument python program.py + +The code in ``program.py`` needs to use one of the packages for which there is +an OpenTelemetry integration. For a list of the available integrations please +check `here `_ + + +opentelemetry-bootstrap +----------------------- + +:: + + opentelemetry-bootstrap --action=install|requirements + +This commands inspects the active Python site-packages and figures out which +instrumentation packages the user might want to install. By default it prints out +a list of the suggested instrumentation packages which can be added to a requirements.txt +file. It also supports installing the suggested packages when run with :code:`--action=install` +flag. + +References +---------- + +* `OpenTelemetry Project `_ diff --git a/opentelemetry-instrumentation/setup.cfg b/opentelemetry-instrumentation/setup.cfg new file mode 100644 index 0000000000..46f956f317 --- /dev/null +++ b/opentelemetry-instrumentation/setup.cfg @@ -0,0 +1,57 @@ +# Copyright The OpenTelemetry Authors +# +# 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. +# +[metadata] +name = opentelemetry-instrumentation +description = Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python +long_description = file: README.rst +long_description_content_type = text/x-rst +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +url = https://github.com/open-telemetry/opentelemetry-python/tree/master/opentelemetry-instrumentation +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + +[options] +python_requires = >=3.4 +package_dir= + =src +packages=find_namespace: +zip_safe = False +include_package_data = True +install_requires = + opentelemetry-api == 0.10.dev0 + wrapt >= 1.0.0, < 2.0.0 + +[options.packages.find] +where = src + +[options.entry_points] +console_scripts = + opentelemetry-instrument = opentelemetry.instrumentation.auto_instrumentation:run + opentelemetry-bootstrap = opentelemetry.instrumentation.bootstrap:run + +[options.extras_require] +test = diff --git a/opentelemetry-auto-instrumentation/setup.py b/opentelemetry-instrumentation/setup.py similarity index 91% rename from opentelemetry-auto-instrumentation/setup.py rename to opentelemetry-instrumentation/setup.py index 86f8faedbc..fb3c8ff9f1 100644 --- a/opentelemetry-auto-instrumentation/setup.py +++ b/opentelemetry-instrumentation/setup.py @@ -18,7 +18,7 @@ BASE_DIR = os.path.dirname(__file__) VERSION_FILENAME = os.path.join( - BASE_DIR, "src", "opentelemetry", "auto_instrumentation", "version.py" + BASE_DIR, "src", "opentelemetry", "instrumentation", "version.py" ) PACKAGE_INFO = {} with open(VERSION_FILENAME) as f: diff --git a/opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/auto_instrumentation.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation.py similarity index 100% rename from opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/auto_instrumentation.py rename to opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation.py diff --git a/opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/bootstrap.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap.py similarity index 100% rename from opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/bootstrap.py rename to opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap.py diff --git a/opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/instrumentor.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/instrumentor.py similarity index 92% rename from opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/instrumentor.py rename to opentelemetry-instrumentation/src/opentelemetry/instrumentation/instrumentor.py index dd58775bc7..ccd14d142a 100644 --- a/opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/instrumentor.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/instrumentor.py @@ -28,7 +28,7 @@ class BaseInstrumentor(ABC): Child classes of this ABC should instrument specific third party libraries or frameworks either by using the - ``opentelemetry-auto-instrumentation`` command or by calling their methods + ``opentelemetry-instrument`` command or by calling their methods directly. Since every third party library or framework is different and has different @@ -58,7 +58,7 @@ def instrument(self, **kwargs): """Instrument the library This method will be called without any optional arguments by the - ``opentelemetry-auto-instrumentation`` command. The configuration of + ``opentelemetry-instrument`` command. The configuration of the instrumentation when done in this way should be done by previously setting the configuration (using environment variables or any other mechanism) that will be used later by the code in the ``instrument`` @@ -69,7 +69,7 @@ def instrument(self, **kwargs): This means that calling this method directly without passing any optional values should do the very same thing that the - ``opentelemetry-auto-instrumentation`` command does. This approach is + ``opentelemetry-instrument`` command does. This approach is followed because the ``Configuration`` object is immutable. """ diff --git a/opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/sitecustomize.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/sitecustomize.py similarity index 100% rename from opentelemetry-auto-instrumentation/src/opentelemetry/auto_instrumentation/sitecustomize.py rename to opentelemetry-instrumentation/src/opentelemetry/instrumentation/sitecustomize.py diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py new file mode 100644 index 0000000000..8553b1bc63 --- /dev/null +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py @@ -0,0 +1,67 @@ +# Copyright The OpenTelemetry Authors +# +# 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 wrapt import ObjectProxy + +from opentelemetry.trace.status import StatusCanonicalCode + + +def http_status_to_canonical_code( + status: int, allow_redirect: bool = True +) -> StatusCanonicalCode: + """Converts an HTTP status code to an OpenTelemetry canonical status code + + Args: + status (int): HTTP status code + """ + # pylint:disable=too-many-branches,too-many-return-statements + if status < 100: + return StatusCanonicalCode.UNKNOWN + if status <= 299: + return StatusCanonicalCode.OK + if status <= 399: + if allow_redirect: + return StatusCanonicalCode.OK + return StatusCanonicalCode.DEADLINE_EXCEEDED + if status <= 499: + if status == 401: # HTTPStatus.UNAUTHORIZED: + return StatusCanonicalCode.UNAUTHENTICATED + if status == 403: # HTTPStatus.FORBIDDEN: + return StatusCanonicalCode.PERMISSION_DENIED + if status == 404: # HTTPStatus.NOT_FOUND: + return StatusCanonicalCode.NOT_FOUND + if status == 429: # HTTPStatus.TOO_MANY_REQUESTS: + return StatusCanonicalCode.RESOURCE_EXHAUSTED + return StatusCanonicalCode.INVALID_ARGUMENT + if status <= 599: + if status == 501: # HTTPStatus.NOT_IMPLEMENTED: + return StatusCanonicalCode.UNIMPLEMENTED + if status == 503: # HTTPStatus.SERVICE_UNAVAILABLE: + return StatusCanonicalCode.UNAVAILABLE + if status == 504: # HTTPStatus.GATEWAY_TIMEOUT: + return StatusCanonicalCode.DEADLINE_EXCEEDED + return StatusCanonicalCode.INTERNAL + return StatusCanonicalCode.UNKNOWN + + +def unwrap(obj, attr: str): + """Given a function that was wrapped by wrapt.wrap_function_wrapper, unwrap it + + Args: + obj: Object that holds a reference to the wrapped function + attr (str): Name of the wrapped function + """ + func = getattr(obj, attr, None) + if func and isinstance(func, ObjectProxy) and hasattr(func, "__wrapped__"): + setattr(obj, attr, func.__wrapped__) diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/version.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/version.py new file mode 100644 index 0000000000..6d4fefa599 --- /dev/null +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/version.py @@ -0,0 +1,15 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +__version__ = "0.10.dev0" diff --git a/opentelemetry-instrumentation/tests/__init__.py b/opentelemetry-instrumentation/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-auto-instrumentation/tests/test_bootstrap.py b/opentelemetry-instrumentation/tests/test_bootstrap.py similarity index 89% rename from opentelemetry-auto-instrumentation/tests/test_bootstrap.py rename to opentelemetry-instrumentation/tests/test_bootstrap.py index 8af684084b..e5a1a86dda 100644 --- a/opentelemetry-auto-instrumentation/tests/test_bootstrap.py +++ b/opentelemetry-instrumentation/tests/test_bootstrap.py @@ -19,7 +19,7 @@ from unittest import TestCase from unittest.mock import call, patch -from opentelemetry.auto_instrumentation import bootstrap +from opentelemetry.instrumentation import bootstrap def sample_packages(packages, rate): @@ -45,7 +45,7 @@ def setUpClass(cls): ) cls.pkg_patcher = patch( - "opentelemetry.auto_instrumentation.bootstrap._find_installed_libraries", + "opentelemetry.instrumentation.bootstrap._find_installed_libraries", return_value=cls.installed_libraries, ) @@ -57,17 +57,17 @@ def setUpClass(cls): pip_freeze_output.append(inst) cls.pip_freeze_patcher = patch( - "opentelemetry.auto_instrumentation.bootstrap._sys_pip_freeze", + "opentelemetry.instrumentation.bootstrap._sys_pip_freeze", return_value="\n".join(pip_freeze_output), ) cls.pip_install_patcher = patch( - "opentelemetry.auto_instrumentation.bootstrap._sys_pip_install", + "opentelemetry.instrumentation.bootstrap._sys_pip_install", ) cls.pip_uninstall_patcher = patch( - "opentelemetry.auto_instrumentation.bootstrap._sys_pip_uninstall", + "opentelemetry.instrumentation.bootstrap._sys_pip_uninstall", ) cls.pip_check_patcher = patch( - "opentelemetry.auto_instrumentation.bootstrap._pip_check", + "opentelemetry.instrumentation.bootstrap._pip_check", ) cls.pkg_patcher.start() diff --git a/opentelemetry-auto-instrumentation/tests/test_instrumentor.py b/opentelemetry-instrumentation/tests/test_instrumentor.py similarity index 95% rename from opentelemetry-auto-instrumentation/tests/test_instrumentor.py rename to opentelemetry-instrumentation/tests/test_instrumentor.py index a768a40eb4..19104a3246 100644 --- a/opentelemetry-auto-instrumentation/tests/test_instrumentor.py +++ b/opentelemetry-instrumentation/tests/test_instrumentor.py @@ -16,7 +16,7 @@ from logging import WARNING from unittest import TestCase -from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor class TestInstrumentor(TestCase): diff --git a/opentelemetry-auto-instrumentation/tests/test_run.py b/opentelemetry-instrumentation/tests/test_run.py similarity index 84% rename from opentelemetry-auto-instrumentation/tests/test_run.py rename to opentelemetry-instrumentation/tests/test_run.py index 8b37882f5b..21f53babc6 100644 --- a/opentelemetry-auto-instrumentation/tests/test_run.py +++ b/opentelemetry-instrumentation/tests/test_run.py @@ -18,7 +18,7 @@ from unittest import TestCase from unittest.mock import patch -from opentelemetry.auto_instrumentation import auto_instrumentation +from opentelemetry.instrumentation import auto_instrumentation class TestRun(TestCase): @@ -27,13 +27,13 @@ class TestRun(TestCase): @classmethod def setUpClass(cls): cls.argv_patcher = patch( - "opentelemetry.auto_instrumentation.auto_instrumentation.argv" + "opentelemetry.instrumentation.auto_instrumentation.argv" ) cls.execl_patcher = patch( - "opentelemetry.auto_instrumentation.auto_instrumentation.execl" + "opentelemetry.instrumentation.auto_instrumentation.execl" ) cls.which_patcher = patch( - "opentelemetry.auto_instrumentation.auto_instrumentation.which" + "opentelemetry.instrumentation.auto_instrumentation.which" ) cls.argv_patcher.start() @@ -91,11 +91,11 @@ def test_single_path(self): class TestExecl(TestCase): @patch( - "opentelemetry.auto_instrumentation.auto_instrumentation.argv", + "opentelemetry.instrumentation.auto_instrumentation.argv", new=[1, 2, 3], ) - @patch("opentelemetry.auto_instrumentation.auto_instrumentation.which") - @patch("opentelemetry.auto_instrumentation.auto_instrumentation.execl") + @patch("opentelemetry.instrumentation.auto_instrumentation.which") + @patch("opentelemetry.instrumentation.auto_instrumentation.execl") def test_execl( self, mock_execl, mock_which ): # pylint: disable=no-self-use diff --git a/opentelemetry-instrumentation/tests/test_utils.py b/opentelemetry-instrumentation/tests/test_utils.py new file mode 100644 index 0000000000..660a6bbe86 --- /dev/null +++ b/opentelemetry-instrumentation/tests/test_utils.py @@ -0,0 +1,56 @@ +# Copyright The OpenTelemetry Authors +# +# 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 http import HTTPStatus + +from opentelemetry.instrumentation.utils import http_status_to_canonical_code +from opentelemetry.test.test_base import TestBase +from opentelemetry.trace.status import StatusCanonicalCode + + +class TestUtils(TestBase): + def test_http_status_to_canonical_code(self): + for status_code, expected in ( + (HTTPStatus.OK, StatusCanonicalCode.OK), + (HTTPStatus.ACCEPTED, StatusCanonicalCode.OK), + (HTTPStatus.IM_USED, StatusCanonicalCode.OK), + (HTTPStatus.MULTIPLE_CHOICES, StatusCanonicalCode.OK), + (HTTPStatus.BAD_REQUEST, StatusCanonicalCode.INVALID_ARGUMENT), + (HTTPStatus.UNAUTHORIZED, StatusCanonicalCode.UNAUTHENTICATED), + (HTTPStatus.FORBIDDEN, StatusCanonicalCode.PERMISSION_DENIED), + (HTTPStatus.NOT_FOUND, StatusCanonicalCode.NOT_FOUND), + ( + HTTPStatus.UNPROCESSABLE_ENTITY, + StatusCanonicalCode.INVALID_ARGUMENT, + ), + ( + HTTPStatus.TOO_MANY_REQUESTS, + StatusCanonicalCode.RESOURCE_EXHAUSTED, + ), + (HTTPStatus.NOT_IMPLEMENTED, StatusCanonicalCode.UNIMPLEMENTED), + (HTTPStatus.SERVICE_UNAVAILABLE, StatusCanonicalCode.UNAVAILABLE), + ( + HTTPStatus.GATEWAY_TIMEOUT, + StatusCanonicalCode.DEADLINE_EXCEEDED, + ), + ( + HTTPStatus.HTTP_VERSION_NOT_SUPPORTED, + StatusCanonicalCode.INTERNAL, + ), + (600, StatusCanonicalCode.UNKNOWN), + (99, StatusCanonicalCode.UNKNOWN), + ): + with self.subTest(status_code=status_code): + actual = http_status_to_canonical_code(int(status_code)) + self.assertEqual(actual, expected, status_code) diff --git a/opentelemetry-proto/CHANGELOG.md b/opentelemetry-proto/CHANGELOG.md new file mode 100644 index 0000000000..896a782491 --- /dev/null +++ b/opentelemetry-proto/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## Unreleased + +## 0.9b0 + +Released 2020-06-10 + +- Initial release diff --git a/opentelemetry-proto/LICENSE b/opentelemetry-proto/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/opentelemetry-proto/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/opentelemetry-proto/MANIFEST.in b/opentelemetry-proto/MANIFEST.in new file mode 100644 index 0000000000..5da7b7fa51 --- /dev/null +++ b/opentelemetry-proto/MANIFEST.in @@ -0,0 +1,9 @@ +graft gen +graft tests +global-exclude *.pyc +global-exclude *.pyo +global-exclude __pycache__/* +include CHANGELOG.md +include MANIFEST.in +include README.rst +include LICENSE diff --git a/opentelemetry-proto/README.rst b/opentelemetry-proto/README.rst new file mode 100644 index 0000000000..48b9661b20 --- /dev/null +++ b/opentelemetry-proto/README.rst @@ -0,0 +1,30 @@ +OpenTelemetry Python Proto +========================== + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/opentelemetry-proto.svg + :target: https://pypi.org/project/opentelemetry-proto/ + +Installation +------------ + +:: + + pip install opentelemetry-proto + +Code Generation +--------------- + +These files were generated automatically. More details on how to generate them +are available here_. + + +.. _here: https://github.com/open-telemetry/opentelemetry-proto + + +References +---------- + +* `OpenTelemetry Project `_ +* `OpenTelemetry Proto `_ diff --git a/opentelemetry-auto-instrumentation/setup.cfg b/opentelemetry-proto/setup.cfg similarity index 77% rename from opentelemetry-auto-instrumentation/setup.cfg rename to opentelemetry-proto/setup.cfg index 0e9207922b..59b0309ad4 100644 --- a/opentelemetry-auto-instrumentation/setup.cfg +++ b/opentelemetry-proto/setup.cfg @@ -13,13 +13,13 @@ # limitations under the License. # [metadata] -name = opentelemetry-auto-instrumentation -description = Auto Instrumentation for OpenTelemetry Python +name = opentelemetry-proto +description = OpenTelemetry Python Proto long_description = file: README.rst long_description_content_type = text/x-rst author = OpenTelemetry Authors author_email = cncf-opentelemetry-contributors@lists.cncf.io -url = https://github.com/open-telemetry/opentelemetry-python/tree/master/opentelemetry-auto-instrumentation +url = https://github.com/open-telemetry/opentelemetry-python/tree/master/opentelemetry-proto platforms = any license = Apache-2.0 classifiers = @@ -41,12 +41,11 @@ package_dir= packages=find_namespace: zip_safe = False include_package_data = True -install_requires = opentelemetry-api == 0.9.dev0 +install_requires = + protobuf~=3.12.2 [options.packages.find] where = src -[options.entry_points] -console_scripts = - opentelemetry-auto-instrumentation = opentelemetry.auto_instrumentation.auto_instrumentation:run - opentelemetry-bootstrap = opentelemetry.auto_instrumentation.bootstrap:run +[options.extras_require] +test = diff --git a/opentelemetry-proto/setup.py b/opentelemetry-proto/setup.py new file mode 100644 index 0000000000..0bb2e6012e --- /dev/null +++ b/opentelemetry-proto/setup.py @@ -0,0 +1,27 @@ +# Copyright The OpenTelemetry Authors +# +# 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 setuptools + +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join( + BASE_DIR, "src", "opentelemetry", "proto", "version.py" +) +PACKAGE_INFO = {} +with open(VERSION_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup(version=PACKAGE_INFO["__version__"],) diff --git a/opentelemetry-proto/src/opentelemetry/proto/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/collector/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2.py new file mode 100644 index 0000000000..96bb34bc8f --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: opentelemetry/proto/collector/metrics/v1/metrics_service.proto + +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from opentelemetry.proto.metrics.v1 import metrics_pb2 as opentelemetry_dot_proto_dot_metrics_dot_v1_dot_metrics__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='opentelemetry/proto/collector/metrics/v1/metrics_service.proto', + package='opentelemetry.proto.collector.metrics.v1', + syntax='proto3', + serialized_options=b'\n+io.opentelemetry.proto.collector.metrics.v1B\023MetricsServiceProtoP\001ZIgithub.com/open-telemetry/opentelemetry-proto/gen/go/collector/metrics/v1', + serialized_pb=b'\n>opentelemetry/proto/collector/metrics/v1/metrics_service.proto\x12(opentelemetry.proto.collector.metrics.v1\x1a,opentelemetry/proto/metrics/v1/metrics.proto\"h\n\x1b\x45xportMetricsServiceRequest\x12I\n\x10resource_metrics\x18\x01 \x03(\x0b\x32/.opentelemetry.proto.metrics.v1.ResourceMetrics\"\x1e\n\x1c\x45xportMetricsServiceResponse2\xac\x01\n\x0eMetricsService\x12\x99\x01\n\x06\x45xport\x12\x45.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest\x1a\x46.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse\"\x00\x42\x8f\x01\n+io.opentelemetry.proto.collector.metrics.v1B\x13MetricsServiceProtoP\x01ZIgithub.com/open-telemetry/opentelemetry-proto/gen/go/collector/metrics/v1b\x06proto3' + , + dependencies=[opentelemetry_dot_proto_dot_metrics_dot_v1_dot_metrics__pb2.DESCRIPTOR,]) + + + + +_EXPORTMETRICSSERVICEREQUEST = _descriptor.Descriptor( + name='ExportMetricsServiceRequest', + full_name='opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='resource_metrics', full_name='opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest.resource_metrics', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=154, + serialized_end=258, +) + + +_EXPORTMETRICSSERVICERESPONSE = _descriptor.Descriptor( + name='ExportMetricsServiceResponse', + full_name='opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=260, + serialized_end=290, +) + +_EXPORTMETRICSSERVICEREQUEST.fields_by_name['resource_metrics'].message_type = opentelemetry_dot_proto_dot_metrics_dot_v1_dot_metrics__pb2._RESOURCEMETRICS +DESCRIPTOR.message_types_by_name['ExportMetricsServiceRequest'] = _EXPORTMETRICSSERVICEREQUEST +DESCRIPTOR.message_types_by_name['ExportMetricsServiceResponse'] = _EXPORTMETRICSSERVICERESPONSE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +ExportMetricsServiceRequest = _reflection.GeneratedProtocolMessageType('ExportMetricsServiceRequest', (_message.Message,), { + 'DESCRIPTOR' : _EXPORTMETRICSSERVICEREQUEST, + '__module__' : 'opentelemetry.proto.collector.metrics.v1.metrics_service_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest) + }) +_sym_db.RegisterMessage(ExportMetricsServiceRequest) + +ExportMetricsServiceResponse = _reflection.GeneratedProtocolMessageType('ExportMetricsServiceResponse', (_message.Message,), { + 'DESCRIPTOR' : _EXPORTMETRICSSERVICERESPONSE, + '__module__' : 'opentelemetry.proto.collector.metrics.v1.metrics_service_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse) + }) +_sym_db.RegisterMessage(ExportMetricsServiceResponse) + + +DESCRIPTOR._options = None + +_METRICSSERVICE = _descriptor.ServiceDescriptor( + name='MetricsService', + full_name='opentelemetry.proto.collector.metrics.v1.MetricsService', + file=DESCRIPTOR, + index=0, + serialized_options=None, + serialized_start=293, + serialized_end=465, + methods=[ + _descriptor.MethodDescriptor( + name='Export', + full_name='opentelemetry.proto.collector.metrics.v1.MetricsService.Export', + index=0, + containing_service=None, + input_type=_EXPORTMETRICSSERVICEREQUEST, + output_type=_EXPORTMETRICSSERVICERESPONSE, + serialized_options=None, + ), +]) +_sym_db.RegisterServiceDescriptor(_METRICSSERVICE) + +DESCRIPTOR.services_by_name['MetricsService'] = _METRICSSERVICE + +# @@protoc_insertion_point(module_scope) diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2_grpc.py b/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2_grpc.py new file mode 100644 index 0000000000..c58f717616 --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/proto/collector/metrics/v1/metrics_service_pb2_grpc.py @@ -0,0 +1,75 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +import grpc + +from opentelemetry.proto.collector.metrics.v1 import metrics_service_pb2 as opentelemetry_dot_proto_dot_collector_dot_metrics_dot_v1_dot_metrics__service__pb2 + + +class MetricsServiceStub(object): + """Service that can be used to push metrics between one Application + instrumented with OpenTelemetry and a collector, or between a collector and a + central collector. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Export = channel.unary_unary( + '/opentelemetry.proto.collector.metrics.v1.MetricsService/Export', + request_serializer=opentelemetry_dot_proto_dot_collector_dot_metrics_dot_v1_dot_metrics__service__pb2.ExportMetricsServiceRequest.SerializeToString, + response_deserializer=opentelemetry_dot_proto_dot_collector_dot_metrics_dot_v1_dot_metrics__service__pb2.ExportMetricsServiceResponse.FromString, + ) + + +class MetricsServiceServicer(object): + """Service that can be used to push metrics between one Application + instrumented with OpenTelemetry and a collector, or between a collector and a + central collector. + """ + + def Export(self, request, context): + """For performance reasons, it is recommended to keep this RPC + alive for the entire life of the application. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_MetricsServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Export': grpc.unary_unary_rpc_method_handler( + servicer.Export, + request_deserializer=opentelemetry_dot_proto_dot_collector_dot_metrics_dot_v1_dot_metrics__service__pb2.ExportMetricsServiceRequest.FromString, + response_serializer=opentelemetry_dot_proto_dot_collector_dot_metrics_dot_v1_dot_metrics__service__pb2.ExportMetricsServiceResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'opentelemetry.proto.collector.metrics.v1.MetricsService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class MetricsService(object): + """Service that can be used to push metrics between one Application + instrumented with OpenTelemetry and a collector, or between a collector and a + central collector. + """ + + @staticmethod + def Export(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/opentelemetry.proto.collector.metrics.v1.MetricsService/Export', + opentelemetry_dot_proto_dot_collector_dot_metrics_dot_v1_dot_metrics__service__pb2.ExportMetricsServiceRequest.SerializeToString, + opentelemetry_dot_proto_dot_collector_dot_metrics_dot_v1_dot_metrics__service__pb2.ExportMetricsServiceResponse.FromString, + options, channel_credentials, + call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/trace/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/collector/trace/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2.py new file mode 100644 index 0000000000..ccdfb345d9 --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: opentelemetry/proto/collector/trace/v1/trace_service.proto + +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from opentelemetry.proto.trace.v1 import trace_pb2 as opentelemetry_dot_proto_dot_trace_dot_v1_dot_trace__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='opentelemetry/proto/collector/trace/v1/trace_service.proto', + package='opentelemetry.proto.collector.trace.v1', + syntax='proto3', + serialized_options=b'\n)io.opentelemetry.proto.collector.trace.v1B\021TraceServiceProtoP\001ZGgithub.com/open-telemetry/opentelemetry-proto/gen/go/collector/trace/v1', + serialized_pb=b'\n:opentelemetry/proto/collector/trace/v1/trace_service.proto\x12&opentelemetry.proto.collector.trace.v1\x1a(opentelemetry/proto/trace/v1/trace.proto\"`\n\x19\x45xportTraceServiceRequest\x12\x43\n\x0eresource_spans\x18\x01 \x03(\x0b\x32+.opentelemetry.proto.trace.v1.ResourceSpans\"\x1c\n\x1a\x45xportTraceServiceResponse2\xa2\x01\n\x0cTraceService\x12\x91\x01\n\x06\x45xport\x12\x41.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest\x1a\x42.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse\"\x00\x42\x89\x01\n)io.opentelemetry.proto.collector.trace.v1B\x11TraceServiceProtoP\x01ZGgithub.com/open-telemetry/opentelemetry-proto/gen/go/collector/trace/v1b\x06proto3' + , + dependencies=[opentelemetry_dot_proto_dot_trace_dot_v1_dot_trace__pb2.DESCRIPTOR,]) + + + + +_EXPORTTRACESERVICEREQUEST = _descriptor.Descriptor( + name='ExportTraceServiceRequest', + full_name='opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='resource_spans', full_name='opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest.resource_spans', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=144, + serialized_end=240, +) + + +_EXPORTTRACESERVICERESPONSE = _descriptor.Descriptor( + name='ExportTraceServiceResponse', + full_name='opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=242, + serialized_end=270, +) + +_EXPORTTRACESERVICEREQUEST.fields_by_name['resource_spans'].message_type = opentelemetry_dot_proto_dot_trace_dot_v1_dot_trace__pb2._RESOURCESPANS +DESCRIPTOR.message_types_by_name['ExportTraceServiceRequest'] = _EXPORTTRACESERVICEREQUEST +DESCRIPTOR.message_types_by_name['ExportTraceServiceResponse'] = _EXPORTTRACESERVICERESPONSE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +ExportTraceServiceRequest = _reflection.GeneratedProtocolMessageType('ExportTraceServiceRequest', (_message.Message,), { + 'DESCRIPTOR' : _EXPORTTRACESERVICEREQUEST, + '__module__' : 'opentelemetry.proto.collector.trace.v1.trace_service_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest) + }) +_sym_db.RegisterMessage(ExportTraceServiceRequest) + +ExportTraceServiceResponse = _reflection.GeneratedProtocolMessageType('ExportTraceServiceResponse', (_message.Message,), { + 'DESCRIPTOR' : _EXPORTTRACESERVICERESPONSE, + '__module__' : 'opentelemetry.proto.collector.trace.v1.trace_service_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse) + }) +_sym_db.RegisterMessage(ExportTraceServiceResponse) + + +DESCRIPTOR._options = None + +_TRACESERVICE = _descriptor.ServiceDescriptor( + name='TraceService', + full_name='opentelemetry.proto.collector.trace.v1.TraceService', + file=DESCRIPTOR, + index=0, + serialized_options=None, + serialized_start=273, + serialized_end=435, + methods=[ + _descriptor.MethodDescriptor( + name='Export', + full_name='opentelemetry.proto.collector.trace.v1.TraceService.Export', + index=0, + containing_service=None, + input_type=_EXPORTTRACESERVICEREQUEST, + output_type=_EXPORTTRACESERVICERESPONSE, + serialized_options=None, + ), +]) +_sym_db.RegisterServiceDescriptor(_TRACESERVICE) + +DESCRIPTOR.services_by_name['TraceService'] = _TRACESERVICE + +# @@protoc_insertion_point(module_scope) diff --git a/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2_grpc.py b/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2_grpc.py new file mode 100644 index 0000000000..1d86433946 --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/proto/collector/trace/v1/trace_service_pb2_grpc.py @@ -0,0 +1,75 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +import grpc + +from opentelemetry.proto.collector.trace.v1 import trace_service_pb2 as opentelemetry_dot_proto_dot_collector_dot_trace_dot_v1_dot_trace__service__pb2 + + +class TraceServiceStub(object): + """Service that can be used to push spans between one Application instrumented with + OpenTelemetry and an collector, or between an collector and a central collector (in this + case spans are sent/received to/from multiple Applications). + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Export = channel.unary_unary( + '/opentelemetry.proto.collector.trace.v1.TraceService/Export', + request_serializer=opentelemetry_dot_proto_dot_collector_dot_trace_dot_v1_dot_trace__service__pb2.ExportTraceServiceRequest.SerializeToString, + response_deserializer=opentelemetry_dot_proto_dot_collector_dot_trace_dot_v1_dot_trace__service__pb2.ExportTraceServiceResponse.FromString, + ) + + +class TraceServiceServicer(object): + """Service that can be used to push spans between one Application instrumented with + OpenTelemetry and an collector, or between an collector and a central collector (in this + case spans are sent/received to/from multiple Applications). + """ + + def Export(self, request, context): + """For performance reasons, it is recommended to keep this RPC + alive for the entire life of the application. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_TraceServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Export': grpc.unary_unary_rpc_method_handler( + servicer.Export, + request_deserializer=opentelemetry_dot_proto_dot_collector_dot_trace_dot_v1_dot_trace__service__pb2.ExportTraceServiceRequest.FromString, + response_serializer=opentelemetry_dot_proto_dot_collector_dot_trace_dot_v1_dot_trace__service__pb2.ExportTraceServiceResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'opentelemetry.proto.collector.trace.v1.TraceService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class TraceService(object): + """Service that can be used to push spans between one Application instrumented with + OpenTelemetry and an collector, or between an collector and a central collector (in this + case spans are sent/received to/from multiple Applications). + """ + + @staticmethod + def Export(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/opentelemetry.proto.collector.trace.v1.TraceService/Export', + opentelemetry_dot_proto_dot_collector_dot_trace_dot_v1_dot_trace__service__pb2.ExportTraceServiceRequest.SerializeToString, + opentelemetry_dot_proto_dot_collector_dot_trace_dot_v1_dot_trace__service__pb2.ExportTraceServiceResponse.FromString, + options, channel_credentials, + call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/opentelemetry-proto/src/opentelemetry/proto/common/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/common/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/src/opentelemetry/proto/common/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/common/v1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/src/opentelemetry/proto/common/v1/common_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/common/v1/common_pb2.py new file mode 100644 index 0000000000..62e951269c --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/proto/common/v1/common_pb2.py @@ -0,0 +1,238 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: opentelemetry/proto/common/v1/common.proto + +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='opentelemetry/proto/common/v1/common.proto', + package='opentelemetry.proto.common.v1', + syntax='proto3', + serialized_options=b'\n io.opentelemetry.proto.common.v1B\013CommonProtoP\001Z>github.com/open-telemetry/opentelemetry-proto/gen/go/common/v1', + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n*opentelemetry/proto/common/v1/common.proto\x12\x1dopentelemetry.proto.common.v1\"\xf5\x01\n\x11\x41ttributeKeyValue\x12\x0b\n\x03key\x18\x01 \x01(\t\x12H\n\x04type\x18\x02 \x01(\x0e\x32:.opentelemetry.proto.common.v1.AttributeKeyValue.ValueType\x12\x14\n\x0cstring_value\x18\x03 \x01(\t\x12\x11\n\tint_value\x18\x04 \x01(\x03\x12\x14\n\x0c\x64ouble_value\x18\x05 \x01(\x01\x12\x12\n\nbool_value\x18\x06 \x01(\x08\"6\n\tValueType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\n\n\x06\x44OUBLE\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\",\n\x0eStringKeyValue\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"7\n\x16InstrumentationLibrary\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\tBq\n io.opentelemetry.proto.common.v1B\x0b\x43ommonProtoP\x01Z>github.com/open-telemetry/opentelemetry-proto/gen/go/common/v1b\x06proto3' +) + + + +_ATTRIBUTEKEYVALUE_VALUETYPE = _descriptor.EnumDescriptor( + name='ValueType', + full_name='opentelemetry.proto.common.v1.AttributeKeyValue.ValueType', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='STRING', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='INT', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='DOUBLE', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='BOOL', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=269, + serialized_end=323, +) +_sym_db.RegisterEnumDescriptor(_ATTRIBUTEKEYVALUE_VALUETYPE) + + +_ATTRIBUTEKEYVALUE = _descriptor.Descriptor( + name='AttributeKeyValue', + full_name='opentelemetry.proto.common.v1.AttributeKeyValue', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='opentelemetry.proto.common.v1.AttributeKeyValue.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='type', full_name='opentelemetry.proto.common.v1.AttributeKeyValue.type', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='string_value', full_name='opentelemetry.proto.common.v1.AttributeKeyValue.string_value', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='int_value', full_name='opentelemetry.proto.common.v1.AttributeKeyValue.int_value', index=3, + number=4, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='double_value', full_name='opentelemetry.proto.common.v1.AttributeKeyValue.double_value', index=4, + number=5, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='bool_value', full_name='opentelemetry.proto.common.v1.AttributeKeyValue.bool_value', index=5, + number=6, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _ATTRIBUTEKEYVALUE_VALUETYPE, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=78, + serialized_end=323, +) + + +_STRINGKEYVALUE = _descriptor.Descriptor( + name='StringKeyValue', + full_name='opentelemetry.proto.common.v1.StringKeyValue', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='opentelemetry.proto.common.v1.StringKeyValue.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='value', full_name='opentelemetry.proto.common.v1.StringKeyValue.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=325, + serialized_end=369, +) + + +_INSTRUMENTATIONLIBRARY = _descriptor.Descriptor( + name='InstrumentationLibrary', + full_name='opentelemetry.proto.common.v1.InstrumentationLibrary', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='opentelemetry.proto.common.v1.InstrumentationLibrary.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='version', full_name='opentelemetry.proto.common.v1.InstrumentationLibrary.version', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=371, + serialized_end=426, +) + +_ATTRIBUTEKEYVALUE.fields_by_name['type'].enum_type = _ATTRIBUTEKEYVALUE_VALUETYPE +_ATTRIBUTEKEYVALUE_VALUETYPE.containing_type = _ATTRIBUTEKEYVALUE +DESCRIPTOR.message_types_by_name['AttributeKeyValue'] = _ATTRIBUTEKEYVALUE +DESCRIPTOR.message_types_by_name['StringKeyValue'] = _STRINGKEYVALUE +DESCRIPTOR.message_types_by_name['InstrumentationLibrary'] = _INSTRUMENTATIONLIBRARY +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +AttributeKeyValue = _reflection.GeneratedProtocolMessageType('AttributeKeyValue', (_message.Message,), { + 'DESCRIPTOR' : _ATTRIBUTEKEYVALUE, + '__module__' : 'opentelemetry.proto.common.v1.common_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.common.v1.AttributeKeyValue) + }) +_sym_db.RegisterMessage(AttributeKeyValue) + +StringKeyValue = _reflection.GeneratedProtocolMessageType('StringKeyValue', (_message.Message,), { + 'DESCRIPTOR' : _STRINGKEYVALUE, + '__module__' : 'opentelemetry.proto.common.v1.common_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.common.v1.StringKeyValue) + }) +_sym_db.RegisterMessage(StringKeyValue) + +InstrumentationLibrary = _reflection.GeneratedProtocolMessageType('InstrumentationLibrary', (_message.Message,), { + 'DESCRIPTOR' : _INSTRUMENTATIONLIBRARY, + '__module__' : 'opentelemetry.proto.common.v1.common_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.common.v1.InstrumentationLibrary) + }) +_sym_db.RegisterMessage(InstrumentationLibrary) + + +DESCRIPTOR._options = None +# @@protoc_insertion_point(module_scope) diff --git a/opentelemetry-proto/src/opentelemetry/proto/metrics/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/metrics/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/src/opentelemetry/proto/metrics/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/metrics/v1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/src/opentelemetry/proto/metrics/v1/metrics_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/metrics/v1/metrics_pb2.py new file mode 100644 index 0000000000..4edaa3609d --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/proto/metrics/v1/metrics_pb2.py @@ -0,0 +1,789 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: opentelemetry/proto/metrics/v1/metrics.proto + +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from opentelemetry.proto.common.v1 import common_pb2 as opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2 +from opentelemetry.proto.resource.v1 import resource_pb2 as opentelemetry_dot_proto_dot_resource_dot_v1_dot_resource__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='opentelemetry/proto/metrics/v1/metrics.proto', + package='opentelemetry.proto.metrics.v1', + syntax='proto3', + serialized_options=b'\n!io.opentelemetry.proto.metrics.v1B\014MetricsProtoP\001Z?github.com/open-telemetry/opentelemetry-proto/gen/go/metrics/v1', + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n,opentelemetry/proto/metrics/v1/metrics.proto\x12\x1eopentelemetry.proto.metrics.v1\x1a*opentelemetry/proto/common/v1/common.proto\x1a.opentelemetry/proto/resource/v1/resource.proto\"\xb6\x01\n\x0fResourceMetrics\x12;\n\x08resource\x18\x01 \x01(\x0b\x32).opentelemetry.proto.resource.v1.Resource\x12\x66\n\x1finstrumentation_library_metrics\x18\x02 \x03(\x0b\x32=.opentelemetry.proto.metrics.v1.InstrumentationLibraryMetrics\"\xb0\x01\n\x1dInstrumentationLibraryMetrics\x12V\n\x17instrumentation_library\x18\x01 \x01(\x0b\x32\x35.opentelemetry.proto.common.v1.InstrumentationLibrary\x12\x37\n\x07metrics\x18\x02 \x03(\x0b\x32&.opentelemetry.proto.metrics.v1.Metric\"\x8f\x03\n\x06Metric\x12K\n\x11metric_descriptor\x18\x01 \x01(\x0b\x32\x30.opentelemetry.proto.metrics.v1.MetricDescriptor\x12I\n\x11int64_data_points\x18\x02 \x03(\x0b\x32..opentelemetry.proto.metrics.v1.Int64DataPoint\x12K\n\x12\x64ouble_data_points\x18\x03 \x03(\x0b\x32/.opentelemetry.proto.metrics.v1.DoubleDataPoint\x12Q\n\x15histogram_data_points\x18\x04 \x03(\x0b\x32\x32.opentelemetry.proto.metrics.v1.HistogramDataPoint\x12M\n\x13summary_data_points\x18\x05 \x03(\x0b\x32\x30.opentelemetry.proto.metrics.v1.SummaryDataPoint\"\xfe\x02\n\x10MetricDescriptor\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0c\n\x04unit\x18\x03 \x01(\t\x12\x43\n\x04type\x18\x04 \x01(\x0e\x32\x35.opentelemetry.proto.metrics.v1.MetricDescriptor.Type\x12Q\n\x0btemporality\x18\x05 \x01(\x0e\x32<.opentelemetry.proto.metrics.v1.MetricDescriptor.Temporality\"K\n\x04Type\x12\x10\n\x0cINVALID_TYPE\x10\x00\x12\t\n\x05INT64\x10\x01\x12\n\n\x06\x44OUBLE\x10\x02\x12\r\n\tHISTOGRAM\x10\x03\x12\x0b\n\x07SUMMARY\x10\x04\"T\n\x0bTemporality\x12\x17\n\x13INVALID_TEMPORALITY\x10\x00\x12\x11\n\rINSTANTANEOUS\x10\x01\x12\t\n\x05\x44\x45LTA\x10\x02\x12\x0e\n\nCUMULATIVE\x10\x03\"\x94\x01\n\x0eInt64DataPoint\x12=\n\x06labels\x18\x01 \x03(\x0b\x32-.opentelemetry.proto.common.v1.StringKeyValue\x12\x1c\n\x14start_time_unix_nano\x18\x02 \x01(\x06\x12\x16\n\x0etime_unix_nano\x18\x03 \x01(\x06\x12\r\n\x05value\x18\x04 \x01(\x03\"\x95\x01\n\x0f\x44oubleDataPoint\x12=\n\x06labels\x18\x01 \x03(\x0b\x32-.opentelemetry.proto.common.v1.StringKeyValue\x12\x1c\n\x14start_time_unix_nano\x18\x02 \x01(\x06\x12\x16\n\x0etime_unix_nano\x18\x03 \x01(\x06\x12\r\n\x05value\x18\x04 \x01(\x01\"\xf1\x03\n\x12HistogramDataPoint\x12=\n\x06labels\x18\x01 \x03(\x0b\x32-.opentelemetry.proto.common.v1.StringKeyValue\x12\x1c\n\x14start_time_unix_nano\x18\x02 \x01(\x06\x12\x16\n\x0etime_unix_nano\x18\x03 \x01(\x06\x12\r\n\x05\x63ount\x18\x04 \x01(\x04\x12\x0b\n\x03sum\x18\x05 \x01(\x01\x12J\n\x07\x62uckets\x18\x06 \x03(\x0b\x32\x39.opentelemetry.proto.metrics.v1.HistogramDataPoint.Bucket\x12\x17\n\x0f\x65xplicit_bounds\x18\x07 \x03(\x01\x1a\xe4\x01\n\x06\x42ucket\x12\r\n\x05\x63ount\x18\x01 \x01(\x04\x12T\n\x08\x65xemplar\x18\x02 \x01(\x0b\x32\x42.opentelemetry.proto.metrics.v1.HistogramDataPoint.Bucket.Exemplar\x1au\n\x08\x45xemplar\x12\r\n\x05value\x18\x01 \x01(\x01\x12\x16\n\x0etime_unix_nano\x18\x02 \x01(\x06\x12\x42\n\x0b\x61ttachments\x18\x03 \x03(\x0b\x32-.opentelemetry.proto.common.v1.StringKeyValue\"\xba\x02\n\x10SummaryDataPoint\x12=\n\x06labels\x18\x01 \x03(\x0b\x32-.opentelemetry.proto.common.v1.StringKeyValue\x12\x1c\n\x14start_time_unix_nano\x18\x02 \x01(\x06\x12\x16\n\x0etime_unix_nano\x18\x03 \x01(\x06\x12\r\n\x05\x63ount\x18\x04 \x01(\x04\x12\x0b\n\x03sum\x18\x05 \x01(\x01\x12]\n\x11percentile_values\x18\x06 \x03(\x0b\x32\x42.opentelemetry.proto.metrics.v1.SummaryDataPoint.ValueAtPercentile\x1a\x36\n\x11ValueAtPercentile\x12\x12\n\npercentile\x18\x01 \x01(\x01\x12\r\n\x05value\x18\x02 \x01(\x01\x42t\n!io.opentelemetry.proto.metrics.v1B\x0cMetricsProtoP\x01Z?github.com/open-telemetry/opentelemetry-proto/gen/go/metrics/v1b\x06proto3' + , + dependencies=[opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2.DESCRIPTOR,opentelemetry_dot_proto_dot_resource_dot_v1_dot_resource__pb2.DESCRIPTOR,]) + + + +_METRICDESCRIPTOR_TYPE = _descriptor.EnumDescriptor( + name='Type', + full_name='opentelemetry.proto.metrics.v1.MetricDescriptor.Type', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='INVALID_TYPE', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='INT64', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='DOUBLE', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='HISTOGRAM', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='SUMMARY', index=4, number=4, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=1160, + serialized_end=1235, +) +_sym_db.RegisterEnumDescriptor(_METRICDESCRIPTOR_TYPE) + +_METRICDESCRIPTOR_TEMPORALITY = _descriptor.EnumDescriptor( + name='Temporality', + full_name='opentelemetry.proto.metrics.v1.MetricDescriptor.Temporality', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='INVALID_TEMPORALITY', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='INSTANTANEOUS', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='DELTA', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='CUMULATIVE', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=1237, + serialized_end=1321, +) +_sym_db.RegisterEnumDescriptor(_METRICDESCRIPTOR_TEMPORALITY) + + +_RESOURCEMETRICS = _descriptor.Descriptor( + name='ResourceMetrics', + full_name='opentelemetry.proto.metrics.v1.ResourceMetrics', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='resource', full_name='opentelemetry.proto.metrics.v1.ResourceMetrics.resource', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='instrumentation_library_metrics', full_name='opentelemetry.proto.metrics.v1.ResourceMetrics.instrumentation_library_metrics', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=173, + serialized_end=355, +) + + +_INSTRUMENTATIONLIBRARYMETRICS = _descriptor.Descriptor( + name='InstrumentationLibraryMetrics', + full_name='opentelemetry.proto.metrics.v1.InstrumentationLibraryMetrics', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='instrumentation_library', full_name='opentelemetry.proto.metrics.v1.InstrumentationLibraryMetrics.instrumentation_library', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='metrics', full_name='opentelemetry.proto.metrics.v1.InstrumentationLibraryMetrics.metrics', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=358, + serialized_end=534, +) + + +_METRIC = _descriptor.Descriptor( + name='Metric', + full_name='opentelemetry.proto.metrics.v1.Metric', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='metric_descriptor', full_name='opentelemetry.proto.metrics.v1.Metric.metric_descriptor', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='int64_data_points', full_name='opentelemetry.proto.metrics.v1.Metric.int64_data_points', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='double_data_points', full_name='opentelemetry.proto.metrics.v1.Metric.double_data_points', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='histogram_data_points', full_name='opentelemetry.proto.metrics.v1.Metric.histogram_data_points', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='summary_data_points', full_name='opentelemetry.proto.metrics.v1.Metric.summary_data_points', index=4, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=537, + serialized_end=936, +) + + +_METRICDESCRIPTOR = _descriptor.Descriptor( + name='MetricDescriptor', + full_name='opentelemetry.proto.metrics.v1.MetricDescriptor', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='opentelemetry.proto.metrics.v1.MetricDescriptor.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='description', full_name='opentelemetry.proto.metrics.v1.MetricDescriptor.description', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='unit', full_name='opentelemetry.proto.metrics.v1.MetricDescriptor.unit', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='type', full_name='opentelemetry.proto.metrics.v1.MetricDescriptor.type', index=3, + number=4, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='temporality', full_name='opentelemetry.proto.metrics.v1.MetricDescriptor.temporality', index=4, + number=5, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _METRICDESCRIPTOR_TYPE, + _METRICDESCRIPTOR_TEMPORALITY, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=939, + serialized_end=1321, +) + + +_INT64DATAPOINT = _descriptor.Descriptor( + name='Int64DataPoint', + full_name='opentelemetry.proto.metrics.v1.Int64DataPoint', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='labels', full_name='opentelemetry.proto.metrics.v1.Int64DataPoint.labels', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='start_time_unix_nano', full_name='opentelemetry.proto.metrics.v1.Int64DataPoint.start_time_unix_nano', index=1, + number=2, type=6, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='time_unix_nano', full_name='opentelemetry.proto.metrics.v1.Int64DataPoint.time_unix_nano', index=2, + number=3, type=6, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='value', full_name='opentelemetry.proto.metrics.v1.Int64DataPoint.value', index=3, + number=4, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1324, + serialized_end=1472, +) + + +_DOUBLEDATAPOINT = _descriptor.Descriptor( + name='DoubleDataPoint', + full_name='opentelemetry.proto.metrics.v1.DoubleDataPoint', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='labels', full_name='opentelemetry.proto.metrics.v1.DoubleDataPoint.labels', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='start_time_unix_nano', full_name='opentelemetry.proto.metrics.v1.DoubleDataPoint.start_time_unix_nano', index=1, + number=2, type=6, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='time_unix_nano', full_name='opentelemetry.proto.metrics.v1.DoubleDataPoint.time_unix_nano', index=2, + number=3, type=6, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='value', full_name='opentelemetry.proto.metrics.v1.DoubleDataPoint.value', index=3, + number=4, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1475, + serialized_end=1624, +) + + +_HISTOGRAMDATAPOINT_BUCKET_EXEMPLAR = _descriptor.Descriptor( + name='Exemplar', + full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint.Bucket.Exemplar', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='value', full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint.Bucket.Exemplar.value', index=0, + number=1, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='time_unix_nano', full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint.Bucket.Exemplar.time_unix_nano', index=1, + number=2, type=6, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='attachments', full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint.Bucket.Exemplar.attachments', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2007, + serialized_end=2124, +) + +_HISTOGRAMDATAPOINT_BUCKET = _descriptor.Descriptor( + name='Bucket', + full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint.Bucket', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='count', full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint.Bucket.count', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='exemplar', full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint.Bucket.exemplar', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_HISTOGRAMDATAPOINT_BUCKET_EXEMPLAR, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1896, + serialized_end=2124, +) + +_HISTOGRAMDATAPOINT = _descriptor.Descriptor( + name='HistogramDataPoint', + full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='labels', full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint.labels', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='start_time_unix_nano', full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint.start_time_unix_nano', index=1, + number=2, type=6, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='time_unix_nano', full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint.time_unix_nano', index=2, + number=3, type=6, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='count', full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint.count', index=3, + number=4, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sum', full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint.sum', index=4, + number=5, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='buckets', full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint.buckets', index=5, + number=6, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='explicit_bounds', full_name='opentelemetry.proto.metrics.v1.HistogramDataPoint.explicit_bounds', index=6, + number=7, type=1, cpp_type=5, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_HISTOGRAMDATAPOINT_BUCKET, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1627, + serialized_end=2124, +) + + +_SUMMARYDATAPOINT_VALUEATPERCENTILE = _descriptor.Descriptor( + name='ValueAtPercentile', + full_name='opentelemetry.proto.metrics.v1.SummaryDataPoint.ValueAtPercentile', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='percentile', full_name='opentelemetry.proto.metrics.v1.SummaryDataPoint.ValueAtPercentile.percentile', index=0, + number=1, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='value', full_name='opentelemetry.proto.metrics.v1.SummaryDataPoint.ValueAtPercentile.value', index=1, + number=2, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2387, + serialized_end=2441, +) + +_SUMMARYDATAPOINT = _descriptor.Descriptor( + name='SummaryDataPoint', + full_name='opentelemetry.proto.metrics.v1.SummaryDataPoint', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='labels', full_name='opentelemetry.proto.metrics.v1.SummaryDataPoint.labels', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='start_time_unix_nano', full_name='opentelemetry.proto.metrics.v1.SummaryDataPoint.start_time_unix_nano', index=1, + number=2, type=6, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='time_unix_nano', full_name='opentelemetry.proto.metrics.v1.SummaryDataPoint.time_unix_nano', index=2, + number=3, type=6, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='count', full_name='opentelemetry.proto.metrics.v1.SummaryDataPoint.count', index=3, + number=4, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='sum', full_name='opentelemetry.proto.metrics.v1.SummaryDataPoint.sum', index=4, + number=5, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='percentile_values', full_name='opentelemetry.proto.metrics.v1.SummaryDataPoint.percentile_values', index=5, + number=6, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_SUMMARYDATAPOINT_VALUEATPERCENTILE, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2127, + serialized_end=2441, +) + +_RESOURCEMETRICS.fields_by_name['resource'].message_type = opentelemetry_dot_proto_dot_resource_dot_v1_dot_resource__pb2._RESOURCE +_RESOURCEMETRICS.fields_by_name['instrumentation_library_metrics'].message_type = _INSTRUMENTATIONLIBRARYMETRICS +_INSTRUMENTATIONLIBRARYMETRICS.fields_by_name['instrumentation_library'].message_type = opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2._INSTRUMENTATIONLIBRARY +_INSTRUMENTATIONLIBRARYMETRICS.fields_by_name['metrics'].message_type = _METRIC +_METRIC.fields_by_name['metric_descriptor'].message_type = _METRICDESCRIPTOR +_METRIC.fields_by_name['int64_data_points'].message_type = _INT64DATAPOINT +_METRIC.fields_by_name['double_data_points'].message_type = _DOUBLEDATAPOINT +_METRIC.fields_by_name['histogram_data_points'].message_type = _HISTOGRAMDATAPOINT +_METRIC.fields_by_name['summary_data_points'].message_type = _SUMMARYDATAPOINT +_METRICDESCRIPTOR.fields_by_name['type'].enum_type = _METRICDESCRIPTOR_TYPE +_METRICDESCRIPTOR.fields_by_name['temporality'].enum_type = _METRICDESCRIPTOR_TEMPORALITY +_METRICDESCRIPTOR_TYPE.containing_type = _METRICDESCRIPTOR +_METRICDESCRIPTOR_TEMPORALITY.containing_type = _METRICDESCRIPTOR +_INT64DATAPOINT.fields_by_name['labels'].message_type = opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2._STRINGKEYVALUE +_DOUBLEDATAPOINT.fields_by_name['labels'].message_type = opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2._STRINGKEYVALUE +_HISTOGRAMDATAPOINT_BUCKET_EXEMPLAR.fields_by_name['attachments'].message_type = opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2._STRINGKEYVALUE +_HISTOGRAMDATAPOINT_BUCKET_EXEMPLAR.containing_type = _HISTOGRAMDATAPOINT_BUCKET +_HISTOGRAMDATAPOINT_BUCKET.fields_by_name['exemplar'].message_type = _HISTOGRAMDATAPOINT_BUCKET_EXEMPLAR +_HISTOGRAMDATAPOINT_BUCKET.containing_type = _HISTOGRAMDATAPOINT +_HISTOGRAMDATAPOINT.fields_by_name['labels'].message_type = opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2._STRINGKEYVALUE +_HISTOGRAMDATAPOINT.fields_by_name['buckets'].message_type = _HISTOGRAMDATAPOINT_BUCKET +_SUMMARYDATAPOINT_VALUEATPERCENTILE.containing_type = _SUMMARYDATAPOINT +_SUMMARYDATAPOINT.fields_by_name['labels'].message_type = opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2._STRINGKEYVALUE +_SUMMARYDATAPOINT.fields_by_name['percentile_values'].message_type = _SUMMARYDATAPOINT_VALUEATPERCENTILE +DESCRIPTOR.message_types_by_name['ResourceMetrics'] = _RESOURCEMETRICS +DESCRIPTOR.message_types_by_name['InstrumentationLibraryMetrics'] = _INSTRUMENTATIONLIBRARYMETRICS +DESCRIPTOR.message_types_by_name['Metric'] = _METRIC +DESCRIPTOR.message_types_by_name['MetricDescriptor'] = _METRICDESCRIPTOR +DESCRIPTOR.message_types_by_name['Int64DataPoint'] = _INT64DATAPOINT +DESCRIPTOR.message_types_by_name['DoubleDataPoint'] = _DOUBLEDATAPOINT +DESCRIPTOR.message_types_by_name['HistogramDataPoint'] = _HISTOGRAMDATAPOINT +DESCRIPTOR.message_types_by_name['SummaryDataPoint'] = _SUMMARYDATAPOINT +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +ResourceMetrics = _reflection.GeneratedProtocolMessageType('ResourceMetrics', (_message.Message,), { + 'DESCRIPTOR' : _RESOURCEMETRICS, + '__module__' : 'opentelemetry.proto.metrics.v1.metrics_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.metrics.v1.ResourceMetrics) + }) +_sym_db.RegisterMessage(ResourceMetrics) + +InstrumentationLibraryMetrics = _reflection.GeneratedProtocolMessageType('InstrumentationLibraryMetrics', (_message.Message,), { + 'DESCRIPTOR' : _INSTRUMENTATIONLIBRARYMETRICS, + '__module__' : 'opentelemetry.proto.metrics.v1.metrics_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.metrics.v1.InstrumentationLibraryMetrics) + }) +_sym_db.RegisterMessage(InstrumentationLibraryMetrics) + +Metric = _reflection.GeneratedProtocolMessageType('Metric', (_message.Message,), { + 'DESCRIPTOR' : _METRIC, + '__module__' : 'opentelemetry.proto.metrics.v1.metrics_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.metrics.v1.Metric) + }) +_sym_db.RegisterMessage(Metric) + +MetricDescriptor = _reflection.GeneratedProtocolMessageType('MetricDescriptor', (_message.Message,), { + 'DESCRIPTOR' : _METRICDESCRIPTOR, + '__module__' : 'opentelemetry.proto.metrics.v1.metrics_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.metrics.v1.MetricDescriptor) + }) +_sym_db.RegisterMessage(MetricDescriptor) + +Int64DataPoint = _reflection.GeneratedProtocolMessageType('Int64DataPoint', (_message.Message,), { + 'DESCRIPTOR' : _INT64DATAPOINT, + '__module__' : 'opentelemetry.proto.metrics.v1.metrics_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.metrics.v1.Int64DataPoint) + }) +_sym_db.RegisterMessage(Int64DataPoint) + +DoubleDataPoint = _reflection.GeneratedProtocolMessageType('DoubleDataPoint', (_message.Message,), { + 'DESCRIPTOR' : _DOUBLEDATAPOINT, + '__module__' : 'opentelemetry.proto.metrics.v1.metrics_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.metrics.v1.DoubleDataPoint) + }) +_sym_db.RegisterMessage(DoubleDataPoint) + +HistogramDataPoint = _reflection.GeneratedProtocolMessageType('HistogramDataPoint', (_message.Message,), { + + 'Bucket' : _reflection.GeneratedProtocolMessageType('Bucket', (_message.Message,), { + + 'Exemplar' : _reflection.GeneratedProtocolMessageType('Exemplar', (_message.Message,), { + 'DESCRIPTOR' : _HISTOGRAMDATAPOINT_BUCKET_EXEMPLAR, + '__module__' : 'opentelemetry.proto.metrics.v1.metrics_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.metrics.v1.HistogramDataPoint.Bucket.Exemplar) + }) + , + 'DESCRIPTOR' : _HISTOGRAMDATAPOINT_BUCKET, + '__module__' : 'opentelemetry.proto.metrics.v1.metrics_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.metrics.v1.HistogramDataPoint.Bucket) + }) + , + 'DESCRIPTOR' : _HISTOGRAMDATAPOINT, + '__module__' : 'opentelemetry.proto.metrics.v1.metrics_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.metrics.v1.HistogramDataPoint) + }) +_sym_db.RegisterMessage(HistogramDataPoint) +_sym_db.RegisterMessage(HistogramDataPoint.Bucket) +_sym_db.RegisterMessage(HistogramDataPoint.Bucket.Exemplar) + +SummaryDataPoint = _reflection.GeneratedProtocolMessageType('SummaryDataPoint', (_message.Message,), { + + 'ValueAtPercentile' : _reflection.GeneratedProtocolMessageType('ValueAtPercentile', (_message.Message,), { + 'DESCRIPTOR' : _SUMMARYDATAPOINT_VALUEATPERCENTILE, + '__module__' : 'opentelemetry.proto.metrics.v1.metrics_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.metrics.v1.SummaryDataPoint.ValueAtPercentile) + }) + , + 'DESCRIPTOR' : _SUMMARYDATAPOINT, + '__module__' : 'opentelemetry.proto.metrics.v1.metrics_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.metrics.v1.SummaryDataPoint) + }) +_sym_db.RegisterMessage(SummaryDataPoint) +_sym_db.RegisterMessage(SummaryDataPoint.ValueAtPercentile) + + +DESCRIPTOR._options = None +# @@protoc_insertion_point(module_scope) diff --git a/opentelemetry-proto/src/opentelemetry/proto/resource/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/resource/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/src/opentelemetry/proto/resource/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/resource/v1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/src/opentelemetry/proto/resource/v1/resource_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/resource/v1/resource_pb2.py new file mode 100644 index 0000000000..5126a1b76e --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/proto/resource/v1/resource_pb2.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: opentelemetry/proto/resource/v1/resource.proto + +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from opentelemetry.proto.common.v1 import common_pb2 as opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='opentelemetry/proto/resource/v1/resource.proto', + package='opentelemetry.proto.resource.v1', + syntax='proto3', + serialized_options=b'\n\"io.opentelemetry.proto.resource.v1B\rResourceProtoP\001Z@github.com/open-telemetry/opentelemetry-proto/gen/go/resource/v1', + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n.opentelemetry/proto/resource/v1/resource.proto\x12\x1fopentelemetry.proto.resource.v1\x1a*opentelemetry/proto/common/v1/common.proto\"r\n\x08Resource\x12\x44\n\nattributes\x18\x01 \x03(\x0b\x32\x30.opentelemetry.proto.common.v1.AttributeKeyValue\x12 \n\x18\x64ropped_attributes_count\x18\x02 \x01(\rBw\n\"io.opentelemetry.proto.resource.v1B\rResourceProtoP\x01Z@github.com/open-telemetry/opentelemetry-proto/gen/go/resource/v1b\x06proto3' + , + dependencies=[opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2.DESCRIPTOR,]) + + + + +_RESOURCE = _descriptor.Descriptor( + name='Resource', + full_name='opentelemetry.proto.resource.v1.Resource', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='attributes', full_name='opentelemetry.proto.resource.v1.Resource.attributes', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='dropped_attributes_count', full_name='opentelemetry.proto.resource.v1.Resource.dropped_attributes_count', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=127, + serialized_end=241, +) + +_RESOURCE.fields_by_name['attributes'].message_type = opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2._ATTRIBUTEKEYVALUE +DESCRIPTOR.message_types_by_name['Resource'] = _RESOURCE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +Resource = _reflection.GeneratedProtocolMessageType('Resource', (_message.Message,), { + 'DESCRIPTOR' : _RESOURCE, + '__module__' : 'opentelemetry.proto.resource.v1.resource_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.resource.v1.Resource) + }) +_sym_db.RegisterMessage(Resource) + + +DESCRIPTOR._options = None +# @@protoc_insertion_point(module_scope) diff --git a/opentelemetry-proto/src/opentelemetry/proto/trace/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/trace/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/src/opentelemetry/proto/trace/v1/__init__.py b/opentelemetry-proto/src/opentelemetry/proto/trace/v1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/src/opentelemetry/proto/trace/v1/trace_config_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/trace/v1/trace_config_pb2.py new file mode 100644 index 0000000000..ad1857d37b --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/proto/trace/v1/trace_config_pb2.py @@ -0,0 +1,290 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: opentelemetry/proto/trace/v1/trace_config.proto + +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='opentelemetry/proto/trace/v1/trace_config.proto', + package='opentelemetry.proto.trace.v1', + syntax='proto3', + serialized_options=b'\n\037io.opentelemetry.proto.trace.v1B\020TraceConfigProtoP\001ZGgithub.com/open-telemetry/opentelemetry-proto/gen/go/collector/trace/v1', + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n/opentelemetry/proto/trace/v1/trace_config.proto\x12\x1copentelemetry.proto.trace.v1\"\xc8\x03\n\x0bTraceConfig\x12I\n\x10\x63onstant_sampler\x18\x01 \x01(\x0b\x32-.opentelemetry.proto.trace.v1.ConstantSamplerH\x00\x12O\n\x13probability_sampler\x18\x02 \x01(\x0b\x32\x30.opentelemetry.proto.trace.v1.ProbabilitySamplerH\x00\x12R\n\x15rate_limiting_sampler\x18\x03 \x01(\x0b\x32\x31.opentelemetry.proto.trace.v1.RateLimitingSamplerH\x00\x12 \n\x18max_number_of_attributes\x18\x04 \x01(\x03\x12\"\n\x1amax_number_of_timed_events\x18\x05 \x01(\x03\x12\x30\n(max_number_of_attributes_per_timed_event\x18\x06 \x01(\x03\x12\x1b\n\x13max_number_of_links\x18\x07 \x01(\x03\x12)\n!max_number_of_attributes_per_link\x18\x08 \x01(\x03\x42\t\n\x07sampler\"\xa9\x01\n\x0f\x43onstantSampler\x12P\n\x08\x64\x65\x63ision\x18\x01 \x01(\x0e\x32>.opentelemetry.proto.trace.v1.ConstantSampler.ConstantDecision\"D\n\x10\x43onstantDecision\x12\x0e\n\nALWAYS_OFF\x10\x00\x12\r\n\tALWAYS_ON\x10\x01\x12\x11\n\rALWAYS_PARENT\x10\x02\"1\n\x12ProbabilitySampler\x12\x1b\n\x13samplingProbability\x18\x01 \x01(\x01\"\"\n\x13RateLimitingSampler\x12\x0b\n\x03qps\x18\x01 \x01(\x03\x42~\n\x1fio.opentelemetry.proto.trace.v1B\x10TraceConfigProtoP\x01ZGgithub.com/open-telemetry/opentelemetry-proto/gen/go/collector/trace/v1b\x06proto3' +) + + + +_CONSTANTSAMPLER_CONSTANTDECISION = _descriptor.EnumDescriptor( + name='ConstantDecision', + full_name='opentelemetry.proto.trace.v1.ConstantSampler.ConstantDecision', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='ALWAYS_OFF', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ALWAYS_ON', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ALWAYS_PARENT', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=642, + serialized_end=710, +) +_sym_db.RegisterEnumDescriptor(_CONSTANTSAMPLER_CONSTANTDECISION) + + +_TRACECONFIG = _descriptor.Descriptor( + name='TraceConfig', + full_name='opentelemetry.proto.trace.v1.TraceConfig', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='constant_sampler', full_name='opentelemetry.proto.trace.v1.TraceConfig.constant_sampler', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='probability_sampler', full_name='opentelemetry.proto.trace.v1.TraceConfig.probability_sampler', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='rate_limiting_sampler', full_name='opentelemetry.proto.trace.v1.TraceConfig.rate_limiting_sampler', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='max_number_of_attributes', full_name='opentelemetry.proto.trace.v1.TraceConfig.max_number_of_attributes', index=3, + number=4, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='max_number_of_timed_events', full_name='opentelemetry.proto.trace.v1.TraceConfig.max_number_of_timed_events', index=4, + number=5, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='max_number_of_attributes_per_timed_event', full_name='opentelemetry.proto.trace.v1.TraceConfig.max_number_of_attributes_per_timed_event', index=5, + number=6, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='max_number_of_links', full_name='opentelemetry.proto.trace.v1.TraceConfig.max_number_of_links', index=6, + number=7, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='max_number_of_attributes_per_link', full_name='opentelemetry.proto.trace.v1.TraceConfig.max_number_of_attributes_per_link', index=7, + number=8, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='sampler', full_name='opentelemetry.proto.trace.v1.TraceConfig.sampler', + index=0, containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[]), + ], + serialized_start=82, + serialized_end=538, +) + + +_CONSTANTSAMPLER = _descriptor.Descriptor( + name='ConstantSampler', + full_name='opentelemetry.proto.trace.v1.ConstantSampler', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='decision', full_name='opentelemetry.proto.trace.v1.ConstantSampler.decision', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _CONSTANTSAMPLER_CONSTANTDECISION, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=541, + serialized_end=710, +) + + +_PROBABILITYSAMPLER = _descriptor.Descriptor( + name='ProbabilitySampler', + full_name='opentelemetry.proto.trace.v1.ProbabilitySampler', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='samplingProbability', full_name='opentelemetry.proto.trace.v1.ProbabilitySampler.samplingProbability', index=0, + number=1, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=712, + serialized_end=761, +) + + +_RATELIMITINGSAMPLER = _descriptor.Descriptor( + name='RateLimitingSampler', + full_name='opentelemetry.proto.trace.v1.RateLimitingSampler', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='qps', full_name='opentelemetry.proto.trace.v1.RateLimitingSampler.qps', index=0, + number=1, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=763, + serialized_end=797, +) + +_TRACECONFIG.fields_by_name['constant_sampler'].message_type = _CONSTANTSAMPLER +_TRACECONFIG.fields_by_name['probability_sampler'].message_type = _PROBABILITYSAMPLER +_TRACECONFIG.fields_by_name['rate_limiting_sampler'].message_type = _RATELIMITINGSAMPLER +_TRACECONFIG.oneofs_by_name['sampler'].fields.append( + _TRACECONFIG.fields_by_name['constant_sampler']) +_TRACECONFIG.fields_by_name['constant_sampler'].containing_oneof = _TRACECONFIG.oneofs_by_name['sampler'] +_TRACECONFIG.oneofs_by_name['sampler'].fields.append( + _TRACECONFIG.fields_by_name['probability_sampler']) +_TRACECONFIG.fields_by_name['probability_sampler'].containing_oneof = _TRACECONFIG.oneofs_by_name['sampler'] +_TRACECONFIG.oneofs_by_name['sampler'].fields.append( + _TRACECONFIG.fields_by_name['rate_limiting_sampler']) +_TRACECONFIG.fields_by_name['rate_limiting_sampler'].containing_oneof = _TRACECONFIG.oneofs_by_name['sampler'] +_CONSTANTSAMPLER.fields_by_name['decision'].enum_type = _CONSTANTSAMPLER_CONSTANTDECISION +_CONSTANTSAMPLER_CONSTANTDECISION.containing_type = _CONSTANTSAMPLER +DESCRIPTOR.message_types_by_name['TraceConfig'] = _TRACECONFIG +DESCRIPTOR.message_types_by_name['ConstantSampler'] = _CONSTANTSAMPLER +DESCRIPTOR.message_types_by_name['ProbabilitySampler'] = _PROBABILITYSAMPLER +DESCRIPTOR.message_types_by_name['RateLimitingSampler'] = _RATELIMITINGSAMPLER +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +TraceConfig = _reflection.GeneratedProtocolMessageType('TraceConfig', (_message.Message,), { + 'DESCRIPTOR' : _TRACECONFIG, + '__module__' : 'opentelemetry.proto.trace.v1.trace_config_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.trace.v1.TraceConfig) + }) +_sym_db.RegisterMessage(TraceConfig) + +ConstantSampler = _reflection.GeneratedProtocolMessageType('ConstantSampler', (_message.Message,), { + 'DESCRIPTOR' : _CONSTANTSAMPLER, + '__module__' : 'opentelemetry.proto.trace.v1.trace_config_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.trace.v1.ConstantSampler) + }) +_sym_db.RegisterMessage(ConstantSampler) + +ProbabilitySampler = _reflection.GeneratedProtocolMessageType('ProbabilitySampler', (_message.Message,), { + 'DESCRIPTOR' : _PROBABILITYSAMPLER, + '__module__' : 'opentelemetry.proto.trace.v1.trace_config_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.trace.v1.ProbabilitySampler) + }) +_sym_db.RegisterMessage(ProbabilitySampler) + +RateLimitingSampler = _reflection.GeneratedProtocolMessageType('RateLimitingSampler', (_message.Message,), { + 'DESCRIPTOR' : _RATELIMITINGSAMPLER, + '__module__' : 'opentelemetry.proto.trace.v1.trace_config_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.trace.v1.RateLimitingSampler) + }) +_sym_db.RegisterMessage(RateLimitingSampler) + + +DESCRIPTOR._options = None +# @@protoc_insertion_point(module_scope) diff --git a/opentelemetry-proto/src/opentelemetry/proto/trace/v1/trace_pb2.py b/opentelemetry-proto/src/opentelemetry/proto/trace/v1/trace_pb2.py new file mode 100644 index 0000000000..cea946eebe --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/proto/trace/v1/trace_pb2.py @@ -0,0 +1,603 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: opentelemetry/proto/trace/v1/trace.proto + +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from opentelemetry.proto.common.v1 import common_pb2 as opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2 +from opentelemetry.proto.resource.v1 import resource_pb2 as opentelemetry_dot_proto_dot_resource_dot_v1_dot_resource__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='opentelemetry/proto/trace/v1/trace.proto', + package='opentelemetry.proto.trace.v1', + syntax='proto3', + serialized_options=b'\n\037io.opentelemetry.proto.trace.v1B\nTraceProtoP\001Z=github.com/open-telemetry/opentelemetry-proto/gen/go/trace/v1', + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n(opentelemetry/proto/trace/v1/trace.proto\x12\x1copentelemetry.proto.trace.v1\x1a*opentelemetry/proto/common/v1/common.proto\x1a.opentelemetry/proto/resource/v1/resource.proto\"\xae\x01\n\rResourceSpans\x12;\n\x08resource\x18\x01 \x01(\x0b\x32).opentelemetry.proto.resource.v1.Resource\x12`\n\x1dinstrumentation_library_spans\x18\x02 \x03(\x0b\x32\x39.opentelemetry.proto.trace.v1.InstrumentationLibrarySpans\"\xa8\x01\n\x1bInstrumentationLibrarySpans\x12V\n\x17instrumentation_library\x18\x01 \x01(\x0b\x32\x35.opentelemetry.proto.common.v1.InstrumentationLibrary\x12\x31\n\x05spans\x18\x02 \x03(\x0b\x32\".opentelemetry.proto.trace.v1.Span\"\xce\x07\n\x04Span\x12\x10\n\x08trace_id\x18\x01 \x01(\x0c\x12\x0f\n\x07span_id\x18\x02 \x01(\x0c\x12\x13\n\x0btrace_state\x18\x03 \x01(\t\x12\x16\n\x0eparent_span_id\x18\x04 \x01(\x0c\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\x39\n\x04kind\x18\x06 \x01(\x0e\x32+.opentelemetry.proto.trace.v1.Span.SpanKind\x12\x1c\n\x14start_time_unix_nano\x18\x07 \x01(\x06\x12\x1a\n\x12\x65nd_time_unix_nano\x18\x08 \x01(\x06\x12\x44\n\nattributes\x18\t \x03(\x0b\x32\x30.opentelemetry.proto.common.v1.AttributeKeyValue\x12 \n\x18\x64ropped_attributes_count\x18\n \x01(\r\x12\x38\n\x06\x65vents\x18\x0b \x03(\x0b\x32(.opentelemetry.proto.trace.v1.Span.Event\x12\x1c\n\x14\x64ropped_events_count\x18\x0c \x01(\r\x12\x36\n\x05links\x18\r \x03(\x0b\x32\'.opentelemetry.proto.trace.v1.Span.Link\x12\x1b\n\x13\x64ropped_links_count\x18\x0e \x01(\r\x12\x34\n\x06status\x18\x0f \x01(\x0b\x32$.opentelemetry.proto.trace.v1.Status\x1a\x95\x01\n\x05\x45vent\x12\x16\n\x0etime_unix_nano\x18\x01 \x01(\x06\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x44\n\nattributes\x18\x03 \x03(\x0b\x32\x30.opentelemetry.proto.common.v1.AttributeKeyValue\x12 \n\x18\x64ropped_attributes_count\x18\x04 \x01(\r\x1a\xa6\x01\n\x04Link\x12\x10\n\x08trace_id\x18\x01 \x01(\x0c\x12\x0f\n\x07span_id\x18\x02 \x01(\x0c\x12\x13\n\x0btrace_state\x18\x03 \x01(\t\x12\x44\n\nattributes\x18\x04 \x03(\x0b\x32\x30.opentelemetry.proto.common.v1.AttributeKeyValue\x12 \n\x18\x64ropped_attributes_count\x18\x05 \x01(\r\"g\n\x08SpanKind\x12\x19\n\x15SPAN_KIND_UNSPECIFIED\x10\x00\x12\x0c\n\x08INTERNAL\x10\x01\x12\n\n\x06SERVER\x10\x02\x12\n\n\x06\x43LIENT\x10\x03\x12\x0c\n\x08PRODUCER\x10\x04\x12\x0c\n\x08\x43ONSUMER\x10\x05\"\x98\x03\n\x06Status\x12=\n\x04\x63ode\x18\x01 \x01(\x0e\x32/.opentelemetry.proto.trace.v1.Status.StatusCode\x12\x0f\n\x07message\x18\x02 \x01(\t\"\xbd\x02\n\nStatusCode\x12\x06\n\x02Ok\x10\x00\x12\r\n\tCancelled\x10\x01\x12\x10\n\x0cUnknownError\x10\x02\x12\x13\n\x0fInvalidArgument\x10\x03\x12\x14\n\x10\x44\x65\x61\x64lineExceeded\x10\x04\x12\x0c\n\x08NotFound\x10\x05\x12\x11\n\rAlreadyExists\x10\x06\x12\x14\n\x10PermissionDenied\x10\x07\x12\x15\n\x11ResourceExhausted\x10\x08\x12\x16\n\x12\x46\x61iledPrecondition\x10\t\x12\x0b\n\x07\x41\x62orted\x10\n\x12\x0e\n\nOutOfRange\x10\x0b\x12\x11\n\rUnimplemented\x10\x0c\x12\x11\n\rInternalError\x10\r\x12\x0f\n\x0bUnavailable\x10\x0e\x12\x0c\n\x08\x44\x61taLoss\x10\x0f\x12\x13\n\x0fUnauthenticated\x10\x10\x42n\n\x1fio.opentelemetry.proto.trace.v1B\nTraceProtoP\x01Z=github.com/open-telemetry/opentelemetry-proto/gen/go/trace/v1b\x06proto3' + , + dependencies=[opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2.DESCRIPTOR,opentelemetry_dot_proto_dot_resource_dot_v1_dot_resource__pb2.DESCRIPTOR,]) + + + +_SPAN_SPANKIND = _descriptor.EnumDescriptor( + name='SpanKind', + full_name='opentelemetry.proto.trace.v1.Span.SpanKind', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='SPAN_KIND_UNSPECIFIED', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='INTERNAL', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='SERVER', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='CLIENT', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='PRODUCER', index=4, number=4, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='CONSUMER', index=5, number=5, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=1386, + serialized_end=1489, +) +_sym_db.RegisterEnumDescriptor(_SPAN_SPANKIND) + +_STATUS_STATUSCODE = _descriptor.EnumDescriptor( + name='StatusCode', + full_name='opentelemetry.proto.trace.v1.Status.StatusCode', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='Ok', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='Cancelled', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='UnknownError', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='InvalidArgument', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='DeadlineExceeded', index=4, number=4, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='NotFound', index=5, number=5, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='AlreadyExists', index=6, number=6, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='PermissionDenied', index=7, number=7, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ResourceExhausted', index=8, number=8, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='FailedPrecondition', index=9, number=9, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='Aborted', index=10, number=10, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='OutOfRange', index=11, number=11, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='Unimplemented', index=12, number=12, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='InternalError', index=13, number=13, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='Unavailable', index=14, number=14, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='DataLoss', index=15, number=15, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='Unauthenticated', index=16, number=16, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=1583, + serialized_end=1900, +) +_sym_db.RegisterEnumDescriptor(_STATUS_STATUSCODE) + + +_RESOURCESPANS = _descriptor.Descriptor( + name='ResourceSpans', + full_name='opentelemetry.proto.trace.v1.ResourceSpans', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='resource', full_name='opentelemetry.proto.trace.v1.ResourceSpans.resource', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='instrumentation_library_spans', full_name='opentelemetry.proto.trace.v1.ResourceSpans.instrumentation_library_spans', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=167, + serialized_end=341, +) + + +_INSTRUMENTATIONLIBRARYSPANS = _descriptor.Descriptor( + name='InstrumentationLibrarySpans', + full_name='opentelemetry.proto.trace.v1.InstrumentationLibrarySpans', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='instrumentation_library', full_name='opentelemetry.proto.trace.v1.InstrumentationLibrarySpans.instrumentation_library', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='spans', full_name='opentelemetry.proto.trace.v1.InstrumentationLibrarySpans.spans', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=344, + serialized_end=512, +) + + +_SPAN_EVENT = _descriptor.Descriptor( + name='Event', + full_name='opentelemetry.proto.trace.v1.Span.Event', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='time_unix_nano', full_name='opentelemetry.proto.trace.v1.Span.Event.time_unix_nano', index=0, + number=1, type=6, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='name', full_name='opentelemetry.proto.trace.v1.Span.Event.name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='attributes', full_name='opentelemetry.proto.trace.v1.Span.Event.attributes', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='dropped_attributes_count', full_name='opentelemetry.proto.trace.v1.Span.Event.dropped_attributes_count', index=3, + number=4, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1066, + serialized_end=1215, +) + +_SPAN_LINK = _descriptor.Descriptor( + name='Link', + full_name='opentelemetry.proto.trace.v1.Span.Link', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='trace_id', full_name='opentelemetry.proto.trace.v1.Span.Link.trace_id', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='span_id', full_name='opentelemetry.proto.trace.v1.Span.Link.span_id', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='trace_state', full_name='opentelemetry.proto.trace.v1.Span.Link.trace_state', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='attributes', full_name='opentelemetry.proto.trace.v1.Span.Link.attributes', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='dropped_attributes_count', full_name='opentelemetry.proto.trace.v1.Span.Link.dropped_attributes_count', index=4, + number=5, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1218, + serialized_end=1384, +) + +_SPAN = _descriptor.Descriptor( + name='Span', + full_name='opentelemetry.proto.trace.v1.Span', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='trace_id', full_name='opentelemetry.proto.trace.v1.Span.trace_id', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='span_id', full_name='opentelemetry.proto.trace.v1.Span.span_id', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='trace_state', full_name='opentelemetry.proto.trace.v1.Span.trace_state', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='parent_span_id', full_name='opentelemetry.proto.trace.v1.Span.parent_span_id', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='name', full_name='opentelemetry.proto.trace.v1.Span.name', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='kind', full_name='opentelemetry.proto.trace.v1.Span.kind', index=5, + number=6, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='start_time_unix_nano', full_name='opentelemetry.proto.trace.v1.Span.start_time_unix_nano', index=6, + number=7, type=6, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='end_time_unix_nano', full_name='opentelemetry.proto.trace.v1.Span.end_time_unix_nano', index=7, + number=8, type=6, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='attributes', full_name='opentelemetry.proto.trace.v1.Span.attributes', index=8, + number=9, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='dropped_attributes_count', full_name='opentelemetry.proto.trace.v1.Span.dropped_attributes_count', index=9, + number=10, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='events', full_name='opentelemetry.proto.trace.v1.Span.events', index=10, + number=11, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='dropped_events_count', full_name='opentelemetry.proto.trace.v1.Span.dropped_events_count', index=11, + number=12, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='links', full_name='opentelemetry.proto.trace.v1.Span.links', index=12, + number=13, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='dropped_links_count', full_name='opentelemetry.proto.trace.v1.Span.dropped_links_count', index=13, + number=14, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='status', full_name='opentelemetry.proto.trace.v1.Span.status', index=14, + number=15, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_SPAN_EVENT, _SPAN_LINK, ], + enum_types=[ + _SPAN_SPANKIND, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=515, + serialized_end=1489, +) + + +_STATUS = _descriptor.Descriptor( + name='Status', + full_name='opentelemetry.proto.trace.v1.Status', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='code', full_name='opentelemetry.proto.trace.v1.Status.code', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='message', full_name='opentelemetry.proto.trace.v1.Status.message', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _STATUS_STATUSCODE, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1492, + serialized_end=1900, +) + +_RESOURCESPANS.fields_by_name['resource'].message_type = opentelemetry_dot_proto_dot_resource_dot_v1_dot_resource__pb2._RESOURCE +_RESOURCESPANS.fields_by_name['instrumentation_library_spans'].message_type = _INSTRUMENTATIONLIBRARYSPANS +_INSTRUMENTATIONLIBRARYSPANS.fields_by_name['instrumentation_library'].message_type = opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2._INSTRUMENTATIONLIBRARY +_INSTRUMENTATIONLIBRARYSPANS.fields_by_name['spans'].message_type = _SPAN +_SPAN_EVENT.fields_by_name['attributes'].message_type = opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2._ATTRIBUTEKEYVALUE +_SPAN_EVENT.containing_type = _SPAN +_SPAN_LINK.fields_by_name['attributes'].message_type = opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2._ATTRIBUTEKEYVALUE +_SPAN_LINK.containing_type = _SPAN +_SPAN.fields_by_name['kind'].enum_type = _SPAN_SPANKIND +_SPAN.fields_by_name['attributes'].message_type = opentelemetry_dot_proto_dot_common_dot_v1_dot_common__pb2._ATTRIBUTEKEYVALUE +_SPAN.fields_by_name['events'].message_type = _SPAN_EVENT +_SPAN.fields_by_name['links'].message_type = _SPAN_LINK +_SPAN.fields_by_name['status'].message_type = _STATUS +_SPAN_SPANKIND.containing_type = _SPAN +_STATUS.fields_by_name['code'].enum_type = _STATUS_STATUSCODE +_STATUS_STATUSCODE.containing_type = _STATUS +DESCRIPTOR.message_types_by_name['ResourceSpans'] = _RESOURCESPANS +DESCRIPTOR.message_types_by_name['InstrumentationLibrarySpans'] = _INSTRUMENTATIONLIBRARYSPANS +DESCRIPTOR.message_types_by_name['Span'] = _SPAN +DESCRIPTOR.message_types_by_name['Status'] = _STATUS +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +ResourceSpans = _reflection.GeneratedProtocolMessageType('ResourceSpans', (_message.Message,), { + 'DESCRIPTOR' : _RESOURCESPANS, + '__module__' : 'opentelemetry.proto.trace.v1.trace_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.trace.v1.ResourceSpans) + }) +_sym_db.RegisterMessage(ResourceSpans) + +InstrumentationLibrarySpans = _reflection.GeneratedProtocolMessageType('InstrumentationLibrarySpans', (_message.Message,), { + 'DESCRIPTOR' : _INSTRUMENTATIONLIBRARYSPANS, + '__module__' : 'opentelemetry.proto.trace.v1.trace_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.trace.v1.InstrumentationLibrarySpans) + }) +_sym_db.RegisterMessage(InstrumentationLibrarySpans) + +Span = _reflection.GeneratedProtocolMessageType('Span', (_message.Message,), { + + 'Event' : _reflection.GeneratedProtocolMessageType('Event', (_message.Message,), { + 'DESCRIPTOR' : _SPAN_EVENT, + '__module__' : 'opentelemetry.proto.trace.v1.trace_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.trace.v1.Span.Event) + }) + , + + 'Link' : _reflection.GeneratedProtocolMessageType('Link', (_message.Message,), { + 'DESCRIPTOR' : _SPAN_LINK, + '__module__' : 'opentelemetry.proto.trace.v1.trace_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.trace.v1.Span.Link) + }) + , + 'DESCRIPTOR' : _SPAN, + '__module__' : 'opentelemetry.proto.trace.v1.trace_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.trace.v1.Span) + }) +_sym_db.RegisterMessage(Span) +_sym_db.RegisterMessage(Span.Event) +_sym_db.RegisterMessage(Span.Link) + +Status = _reflection.GeneratedProtocolMessageType('Status', (_message.Message,), { + 'DESCRIPTOR' : _STATUS, + '__module__' : 'opentelemetry.proto.trace.v1.trace_pb2' + # @@protoc_insertion_point(class_scope:opentelemetry.proto.trace.v1.Status) + }) +_sym_db.RegisterMessage(Status) + + +DESCRIPTOR._options = None +# @@protoc_insertion_point(module_scope) diff --git a/opentelemetry-proto/src/opentelemetry/proto/version.py b/opentelemetry-proto/src/opentelemetry/proto/version.py new file mode 100644 index 0000000000..6d4fefa599 --- /dev/null +++ b/opentelemetry-proto/src/opentelemetry/proto/version.py @@ -0,0 +1,15 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +__version__ = "0.10.dev0" diff --git a/opentelemetry-proto/tests/__init__.py b/opentelemetry-proto/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/opentelemetry-proto/tests/test_proto.py b/opentelemetry-proto/tests/test_proto.py new file mode 100644 index 0000000000..6551e4640f --- /dev/null +++ b/opentelemetry-proto/tests/test_proto.py @@ -0,0 +1,27 @@ +# Copyright The OpenTelemetry Authors +# +# 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. +# type: ignore + +from unittest import TestCase + +from pkg_resources import DistributionNotFound, require + + +class TestInstrumentor(TestCase): + def test_proto(self): + + try: + require(["opentelemetry-proto"]) + except DistributionNotFound: + self.fail("opentelemetry-proto not installed") diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index 0e982b6bc1..7c077dbf5e 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -2,12 +2,27 @@ ## Unreleased +- Rename CounterAggregator -> SumAggregator + ([#816](https://github.com/open-telemetry/opentelemetry-python/pull/816)) + +## 0.9b0 + +Released 2020-06-10 + - Move stateful & resource from Meter to MeterProvider ([#751](https://github.com/open-telemetry/opentelemetry-python/pull/751)) - Rename Measure to ValueRecorder in metrics ([#761](https://github.com/open-telemetry/opentelemetry-python/pull/761)) +- Adding trace.get_current_span, Removing Tracer.get_current_span + ([#552](https://github.com/open-telemetry/opentelemetry-python/pull/552)) - bugfix: byte type attributes are decoded before adding to attributes dict ([#775](https://github.com/open-telemetry/opentelemetry-python/pull/775)) +- Rename Observer to ValueObserver + ([#764](https://github.com/open-telemetry/opentelemetry-python/pull/764)) +- Add SumObserver, UpDownSumObserver and LastValueAggregator in metrics + ([#789](https://github.com/open-telemetry/opentelemetry-python/pull/789)) +- Add start_pipeline to MeterProvider + ([#791](https://github.com/open-telemetry/opentelemetry-python/pull/791)) ## 0.8b0 diff --git a/opentelemetry-sdk/setup.cfg b/opentelemetry-sdk/setup.cfg index 323bca728f..68c9984dc9 100644 --- a/opentelemetry-sdk/setup.cfg +++ b/opentelemetry-sdk/setup.cfg @@ -41,7 +41,8 @@ package_dir= packages=find_namespace: zip_safe = False include_package_data = True -install_requires = opentelemetry-api==0.9.dev0 +install_requires = + opentelemetry-api == 0.10.dev0 [options.packages.find] where = src @@ -52,3 +53,6 @@ opentelemetry_meter_provider = sdk_meter_provider = opentelemetry.sdk.metrics:MeterProvider opentelemetry_tracer_provider = sdk_tracer_provider = opentelemetry.sdk.trace:TracerProvider + +[options.extras_require] +test = diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py index f231182cc2..62c616a3e2 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py @@ -12,13 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +import atexit import logging import threading from typing import Dict, Sequence, Tuple, Type from opentelemetry import metrics as metrics_api +from opentelemetry.sdk.metrics.export import ( + ConsoleMetricsExporter, + MetricsExporter, +) from opentelemetry.sdk.metrics.export.aggregate import Aggregator from opentelemetry.sdk.metrics.export.batcher import UngroupedBatcher +from opentelemetry.sdk.metrics.export.controller import PushController from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.util.instrumentation import InstrumentationInfo @@ -96,6 +102,25 @@ def add(self, value: metrics_api.ValueT) -> None: if self._validate_update(value): self.update(value) + def _validate_update(self, value: metrics_api.ValueT) -> bool: + if not super()._validate_update(value): + return False + if value < 0: + logger.warning( + "Invalid value %s passed to Counter, value must be non-negative. " + "For a Counter that can decrease, use UpDownCounter.", + value, + ) + return False + return True + + +class BoundUpDownCounter(metrics_api.BoundUpDownCounter, BaseBoundInstrument): + def add(self, value: metrics_api.ValueT) -> None: + """See `opentelemetry.metrics.BoundUpDownCounter.add`.""" + if self._validate_update(value): + self.update(value) + class BoundValueRecorder(metrics_api.BoundValueRecorder, BaseBoundInstrument): def record(self, value: metrics_api.ValueT) -> None: @@ -105,12 +130,16 @@ def record(self, value: metrics_api.ValueT) -> None: class Metric(metrics_api.Metric): - """Base class for all metric types. + """Base class for all synchronous metric types. - Also known as metric instrument. This is the class that is used to - represent a metric that is to be continuously recorded and tracked. Each - metric has a set of bound metrics that are created from the metric. See - `BaseBoundInstrument` for information on bound metric instruments. + This is the class that is used to represent a metric that is to be + synchronously recorded and tracked. Synchronous instruments are called + inside a request, meaning they have an associated distributed context + (i.e. Span context, correlation context). Multiple metric events may occur + for a synchronous instrument within a give collection interval. + + Each metric has a set of bound metrics that are created from the metric. + See `BaseBoundInstrument` for information on bound metric instruments. """ BOUND_INSTR_TYPE = BaseBoundInstrument @@ -174,6 +203,21 @@ def add(self, value: metrics_api.ValueT, labels: Dict[str, str]) -> None: UPDATE_FUNCTION = add +class UpDownCounter(Metric, metrics_api.UpDownCounter): + """See `opentelemetry.metrics.UpDownCounter`. + """ + + BOUND_INSTR_TYPE = BoundUpDownCounter + + def add(self, value: metrics_api.ValueT, labels: Dict[str, str]) -> None: + """See `opentelemetry.metrics.UpDownCounter.add`.""" + bound_intrument = self.bind(labels) + bound_intrument.add(value) + bound_intrument.release() + + UPDATE_FUNCTION = add + + class ValueRecorder(Metric, metrics_api.ValueRecorder): """See `opentelemetry.metrics.ValueRecorder`.""" @@ -191,7 +235,13 @@ def record( class Observer(metrics_api.Observer): - """See `opentelemetry.metrics.Observer`.""" + """Base class for all asynchronous metric types. + + Also known as Observers, observer metric instruments are asynchronous in + that they are reported by a callback, once per collection interval, and + lack context. They are permitted to report only one value per distinct + label set per period. + """ def __init__( self, @@ -218,15 +268,10 @@ def __init__( def observe( self, value: metrics_api.ValueT, labels: Dict[str, str] ) -> None: - if not self.enabled: - return - if not isinstance(value, self.value_type): - logger.warning( - "Invalid value passed for %s.", self.value_type.__name__ - ) + key = get_labels_as_key(labels) + if not self._validate_observe(value, key): return - key = get_labels_as_key(labels) if key not in self.aggregators: # TODO: how to cleanup aggregators? self.aggregators[key] = self.meter.batcher.aggregator_for( @@ -235,6 +280,20 @@ def observe( aggregator = self.aggregators[key] aggregator.update(value) + # pylint: disable=W0613 + def _validate_observe( + self, value: metrics_api.ValueT, key: Tuple[Tuple[str, str]], + ) -> bool: + if not self.enabled: + return False + if not isinstance(value, self.value_type): + logger.warning( + "Invalid value passed for %s.", self.value_type.__name__ + ) + return False + + return True + def run(self) -> bool: try: self.callback(self) @@ -252,16 +311,43 @@ def __repr__(self): ) +class SumObserver(Observer, metrics_api.SumObserver): + """See `opentelemetry.metrics.SumObserver`.""" + + def _validate_observe( + self, value: metrics_api.ValueT, key: Tuple[Tuple[str, str]], + ) -> bool: + if not super()._validate_observe(value, key): + return False + # Must be non-decreasing because monotonic + if ( + key in self.aggregators + and self.aggregators[key].current is not None + ): + if value < self.aggregators[key].current: + logger.warning("Value passed must be non-decreasing.") + return False + return True + + +class UpDownSumObserver(Observer, metrics_api.UpDownSumObserver): + """See `opentelemetry.metrics.UpDownSumObserver`.""" + + +class ValueObserver(Observer, metrics_api.ValueObserver): + """See `opentelemetry.metrics.ValueObserver`.""" + + class Record: """Container class used for processing in the `Batcher`""" def __init__( self, - metric: metrics_api.MetricT, + instrument: metrics_api.InstrumentT, labels: Dict[str, str], aggregator: Aggregator, ): - self.metric = metric + self.instrument = instrument self.labels = labels self.aggregator = aggregator @@ -374,10 +460,11 @@ def register_observer( description: str, unit: str, value_type: Type[metrics_api.ValueT], + observer_type=Type[metrics_api.ObserverT], label_keys: Sequence[str] = (), enabled: bool = True, ) -> metrics_api.Observer: - ob = Observer( + ob = observer_type( callback, name, description, @@ -391,7 +478,7 @@ def register_observer( self.observers.add(ob) return ob - def unregister_observer(self, observer: "Observer") -> None: + def unregister_observer(self, observer: metrics_api.Observer) -> None: with self.observers_lock: self.observers.remove(observer) @@ -402,24 +489,64 @@ class MeterProvider(metrics_api.MeterProvider): Args: stateful: Indicates whether meters created are going to be stateful resource: Resource for this MeterProvider + shutdown_on_exit: Register an atexit hook to shut down when the + application exists """ def __init__( - self, stateful=True, resource: Resource = Resource.create_empty(), + self, + stateful=True, + resource: Resource = Resource.create_empty(), + shutdown_on_exit: bool = True, ): self.stateful = stateful self.resource = resource + self._controllers = [] + self._exporters = set() + self._atexit_handler = None + if shutdown_on_exit: + self._atexit_handler = atexit.register(self.shutdown) def get_meter( self, instrumenting_module_name: str, instrumenting_library_version: str = "", ) -> "metrics_api.Meter": + """See `opentelemetry.metrics.MeterProvider`.get_meter.""" if not instrumenting_module_name: # Reject empty strings too. - raise ValueError("get_meter called with missing module name.") + instrumenting_module_name = "ERROR:MISSING MODULE NAME" + logger.error("get_meter called with missing module name.") return Meter( self, InstrumentationInfo( - instrumenting_module_name, instrumenting_library_version + instrumenting_module_name, instrumenting_library_version, ), ) + + def start_pipeline( + self, + meter: metrics_api.Meter, + exporter: MetricsExporter = None, + interval: float = 15.0, + ) -> None: + """Method to begin the collect/export pipeline. + + Args: + meter: The meter to collect metrics from. + exporter: The exporter to export metrics to. + interval: The collect/export interval in seconds. + """ + if not exporter: + exporter = ConsoleMetricsExporter() + self._exporters.add(exporter) + # TODO: Controller type configurable? + self._controllers.append(PushController(meter, exporter, interval)) + + def shutdown(self) -> None: + for controller in self._controllers: + controller.shutdown() + for exporter in self._exporters: + exporter.shutdown() + if self._atexit_handler is not None: + atexit.unregister(self._atexit_handler) + self._atexit_handler = None diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/__init__.py index f5a8693268..16911f94ef 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/__init__.py @@ -27,13 +27,13 @@ class MetricsExportResult(Enum): class MetricRecord: def __init__( self, - aggregator: Aggregator, + instrument: metrics_api.InstrumentT, labels: Tuple[Tuple[str, str]], - metric: metrics_api.MetricT, + aggregator: Aggregator, ): - self.aggregator = aggregator + self.instrument = instrument self.labels = labels - self.metric = metric + self.aggregator = aggregator class MetricsExporter: @@ -79,7 +79,7 @@ def export( print( '{}(data="{}", labels="{}", value={})'.format( type(self).__name__, - record.metric, + record.instrument, record.labels, record.aggregator.checkpoint, ) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/aggregate.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/aggregate.py index 7e1baba2c7..cfea391019 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/aggregate.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/aggregate.py @@ -43,7 +43,7 @@ def merge(self, other): """Combines two aggregator values.""" -class CounterAggregator(Aggregator): +class SumAggregator(Aggregator): """Aggregator for Counter metrics.""" def __init__(self): @@ -125,7 +125,35 @@ def merge(self, other): ) -class ObserverAggregator(Aggregator): +class LastValueAggregator(Aggregator): + """Aggregator that stores last value results.""" + + def __init__(self): + super().__init__() + self._lock = threading.Lock() + self.last_update_timestamp = None + + def update(self, value): + with self._lock: + self.current = value + self.last_update_timestamp = time_ns() + + def take_checkpoint(self): + with self._lock: + self.checkpoint = self.current + self.current = None + + def merge(self, other): + last = self.checkpoint.last + self.last_update_timestamp = get_latest_timestamp( + self.last_update_timestamp, other.last_update_timestamp + ) + if self.last_update_timestamp == other.last_update_timestamp: + last = other.checkpoint.last + self.checkpoint = last + + +class ValueObserverAggregator(Aggregator): """Same as MinMaxSumCount but also with last value.""" _TYPE = namedtuple("minmaxsumcountlast", "min max sum count last") diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/batcher.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/batcher.py index eda504d568..527760d51b 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/batcher.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/batcher.py @@ -15,13 +15,22 @@ import abc from typing import Sequence, Type -from opentelemetry.metrics import Counter, MetricT, Observer, ValueRecorder +from opentelemetry.metrics import ( + Counter, + InstrumentT, + SumObserver, + UpDownCounter, + UpDownSumObserver, + ValueObserver, + ValueRecorder, +) from opentelemetry.sdk.metrics.export import MetricRecord from opentelemetry.sdk.metrics.export.aggregate import ( Aggregator, - CounterAggregator, + LastValueAggregator, MinMaxSumCountAggregator, - ObserverAggregator, + SumAggregator, + ValueObserverAggregator, ) @@ -41,20 +50,22 @@ def __init__(self, stateful: bool): # (deltas) self.stateful = stateful - def aggregator_for(self, metric_type: Type[MetricT]) -> Aggregator: - """Returns an aggregator based on metric type. + def aggregator_for(self, instrument_type: Type[InstrumentT]) -> Aggregator: + """Returns an aggregator based on metric instrument type. Aggregators keep track of and updates values when metrics get updated. """ # pylint:disable=R0201 - if issubclass(metric_type, Counter): - return CounterAggregator() - if issubclass(metric_type, ValueRecorder): + if issubclass(instrument_type, (Counter, UpDownCounter)): + return SumAggregator() + if issubclass(instrument_type, (SumObserver, UpDownSumObserver)): + return LastValueAggregator() + if issubclass(instrument_type, ValueRecorder): return MinMaxSumCountAggregator() - if issubclass(metric_type, Observer): - return ObserverAggregator() + if issubclass(instrument_type, ValueObserver): + return ValueObserverAggregator() # TODO: Add other aggregators - return CounterAggregator() + return SumAggregator() def checkpoint_set(self) -> Sequence[MetricRecord]: """Returns a list of MetricRecords used for exporting. @@ -63,8 +74,8 @@ def checkpoint_set(self) -> Sequence[MetricRecord]: data in all of the aggregators in this batcher. """ metric_records = [] - for (metric, labels), aggregator in self._batch_map.items(): - metric_records.append(MetricRecord(aggregator, labels, metric)) + for (instrument, labels), aggregator in self._batch_map.items(): + metric_records.append(MetricRecord(instrument, labels, aggregator)) return metric_records def finished_collection(self): @@ -90,7 +101,7 @@ class UngroupedBatcher(Batcher): def process(self, record): # Checkpoints the current aggregator value to be collected for export record.aggregator.take_checkpoint() - batch_key = (record.metric, record.labels) + batch_key = (record.instrument, record.labels) batch_value = self._batch_map.get(batch_key) aggregator = record.aggregator if batch_value: @@ -101,6 +112,6 @@ def process(self, record): if self.stateful: # if stateful batcher, create a copy of the aggregator and update # it with the current checkpointed value for long-term storage - aggregator = self.aggregator_for(record.metric.__class__) + aggregator = self.aggregator_for(record.instrument.__class__) aggregator.merge(record.aggregator) self._batch_map[batch_key] = aggregator diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/controller.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/controller.py index 88abed410a..7448f353c4 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/controller.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/controller.py @@ -12,30 +12,35 @@ # See the License for the specific language governing permissions and # limitations under the License. -import atexit import threading from opentelemetry.context import attach, detach, set_value +from opentelemetry.metrics import Meter +from opentelemetry.sdk.metrics.export import MetricsExporter class PushController(threading.Thread): - """A push based controller, used for exporting. + """A push based controller, used for collecting and exporting. Uses a worker thread that periodically collects metrics for exporting, exports them and performs some post-processing. + + Args: + meter: The meter used to collect metrics. + exporter: The exporter used to export metrics. + interval: The collect/export interval in seconds. """ daemon = True - def __init__(self, meter, exporter, interval, shutdown_on_exit=True): + def __init__( + self, meter: Meter, exporter: MetricsExporter, interval: float + ): super().__init__() self.meter = meter self.exporter = exporter self.interval = interval self.finished = threading.Event() - self._atexit_handler = None - if shutdown_on_exit: - self._atexit_handler = atexit.register(self.shutdown) self.start() def run(self): @@ -46,17 +51,13 @@ def shutdown(self): self.finished.set() # Run one more collection pass to flush metrics batched in the meter self.tick() - self.exporter.shutdown() - if self._atexit_handler is not None: - atexit.unregister(self._atexit_handler) - self._atexit_handler = None def tick(self): # Collect all of the meter's metrics to be exported self.meter.collect() + # Export the collected metrics token = attach(set_value("suppress_instrumentation", True)) - # Export the given metrics in the batcher self.exporter.export(self.meter.batcher.checkpoint_set()) detach(token) - # Perform post-exporting logic based on batcher configuration + # Perform post-exporting logic self.meter.batcher.finished_collection() diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index 193f283175..91c491f09f 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -13,6 +13,7 @@ # limitations under the License. import typing +from json import dumps LabelValue = typing.Union[str, bool, int, float] Labels = typing.Dict[str, LabelValue] @@ -49,5 +50,8 @@ def __eq__(self, other: object) -> bool: return False return self._labels == other._labels + def __hash__(self): + return hash(dumps(self._labels, sort_keys=True)) + _EMPTY_RESOURCE = Resource({}) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 980caed37d..db377c0924 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -15,14 +15,26 @@ import abc import atexit +import concurrent.futures import json import logging import random import threading +import traceback from collections import OrderedDict from contextlib import contextmanager from types import TracebackType -from typing import Iterator, MutableSequence, Optional, Sequence, Tuple, Type +from typing import ( + Any, + Callable, + Iterator, + MutableSequence, + Optional, + Sequence, + Tuple, + Type, + Union, +) from opentelemetry import context as context_api from opentelemetry import trace as trace_api @@ -89,9 +101,12 @@ def force_flush(self, timeout_millis: int = 30000) -> bool: """ -class MultiSpanProcessor(SpanProcessor): - """Implementation of :class:`SpanProcessor` that forwards all received - events to a list of `SpanProcessor`. +class SynchronousMultiSpanProcessor(SpanProcessor): + """Implementation of class:`SpanProcessor` that forwards all received + events to a list of span processors sequentially. + + The underlying span processors are called in sequential order as they were + added. """ def __init__(self): @@ -114,9 +129,113 @@ def on_end(self, span: "Span") -> None: sp.on_end(span) def shutdown(self) -> None: + """Sequentially shuts down all underlying span processors. + """ for sp in self._span_processors: sp.shutdown() + def force_flush(self, timeout_millis: int = 30000) -> bool: + """Sequentially calls force_flush on all underlying + :class:`SpanProcessor` + + Args: + timeout_millis: The maximum amount of time over all span processors + to wait for spans to be exported. In case the first n span + processors exceeded the timeout followup span processors will be + skipped. + + Returns: + True if all span processors flushed their spans within the + given timeout, False otherwise. + """ + deadline_ns = time_ns() + timeout_millis * 1000000 + for sp in self._span_processors: + current_time_ns = time_ns() + if current_time_ns >= deadline_ns: + return False + + if not sp.force_flush((deadline_ns - current_time_ns) // 1000000): + return False + + return True + + +class ConcurrentMultiSpanProcessor(SpanProcessor): + """Implementation of :class:`SpanProcessor` that forwards all received + events to a list of span processors in parallel. + + Calls to the underlying span processors are forwarded in parallel by + submitting them to a thread pool executor and waiting until each span + processor finished its work. + + Args: + num_threads: The number of threads managed by the thread pool executor + and thus defining how many span processors can work in parallel. + """ + + def __init__(self, num_threads: int = 2): + # use a tuple to avoid race conditions when adding a new span and + # iterating through it on "on_start" and "on_end". + self._span_processors = () # type: Tuple[SpanProcessor, ...] + self._lock = threading.Lock() + self._executor = concurrent.futures.ThreadPoolExecutor( + max_workers=num_threads + ) + + def add_span_processor(self, span_processor: SpanProcessor) -> None: + """Adds a SpanProcessor to the list handled by this instance.""" + with self._lock: + self._span_processors = self._span_processors + (span_processor,) + + def _submit_and_await( + self, func: Callable[[SpanProcessor], Callable[..., None]], *args: Any + ): + futures = [] + for sp in self._span_processors: + future = self._executor.submit(func(sp), *args) + futures.append(future) + for future in futures: + future.result() + + def on_start(self, span: "Span") -> None: + self._submit_and_await(lambda sp: sp.on_start, span) + + def on_end(self, span: "Span") -> None: + self._submit_and_await(lambda sp: sp.on_end, span) + + def shutdown(self) -> None: + """Shuts down all underlying span processors in parallel.""" + self._submit_and_await(lambda sp: sp.shutdown) + + def force_flush(self, timeout_millis: int = 30000) -> bool: + """Calls force_flush on all underlying span processors in parallel. + + Args: + timeout_millis: The maximum amount of time to wait for spans to be + exported. + + Returns: + True if all span processors flushed their spans within the given + timeout, False otherwise. + """ + futures = [] + for sp in self._span_processors: # type: SpanProcessor + future = self._executor.submit(sp.force_flush, timeout_millis) + futures.append(future) + + timeout_sec = timeout_millis / 1e3 + done_futures, not_done_futures = concurrent.futures.wait( + futures, timeout_sec + ) + if not_done_futures: + return False + + for future in done_futures: + if not future.result(): + return False + + return True + class EventBase(abc.ABC): def __init__(self, name: str, timestamp: Optional[int] = None) -> None: @@ -240,7 +359,7 @@ class Span(trace_api.Span): Args: name: The name of the operation this span represents context: The immutable span context - parent: This span's parent's `SpanContext`, or + parent: This span's parent's `opentelemetry.trace.SpanContext`, or null if this is a root span sampler: The sampler used to create this span trace_config: TODO @@ -563,6 +682,17 @@ def __exit__( super().__exit__(exc_type, exc_val, exc_tb) + def record_error(self, err: Exception) -> None: + """Records an error as a span event.""" + self.add_event( + name="error", + attributes={ + "error.type": err.__class__.__name__, + "error.message": str(err), + "error.stack": traceback.format_exc(), + }, + ) + def generate_span_id() -> int: """Get a new random span ID. @@ -599,9 +729,6 @@ def __init__( self.source = source self.instrumentation_info = instrumentation_info - def get_current_span(self): - return self.source.get_current_span() - def start_as_current_span( self, name: str, @@ -624,7 +751,7 @@ def start_span( # pylint: disable=too-many-locals set_status_on_exception: bool = True, ) -> trace_api.Span: if parent is Tracer.CURRENT_SPAN: - parent = self.get_current_span() + parent = trace_api.get_current_span() parent_context = parent if isinstance(parent_context, trace_api.Span): @@ -676,6 +803,7 @@ def start_span( # pylint: disable=too-many-locals # apply sampling decision attributes after initial attributes span_attributes = attributes.copy() span_attributes.update(sampling_decision.attributes) + # pylint:disable=protected-access span = Span( name=name, context=context, @@ -683,7 +811,7 @@ def start_span( # pylint: disable=too-many-locals sampler=self.source.sampler, resource=self.source.resource, attributes=span_attributes, - span_processor=self.source._active_span_processor, # pylint:disable=protected-access + span_processor=self.source._active_span_processor, kind=kind, links=links, instrumentation_info=self.instrumentation_info, @@ -733,8 +861,13 @@ def __init__( sampler: sampling.Sampler = trace_api.sampling.ALWAYS_ON, resource: Resource = Resource.create_empty(), shutdown_on_exit: bool = True, + active_span_processor: Union[ + SynchronousMultiSpanProcessor, ConcurrentMultiSpanProcessor + ] = None, ): - self._active_span_processor = MultiSpanProcessor() + self._active_span_processor = ( + active_span_processor or SynchronousMultiSpanProcessor() + ) self.resource = resource self.sampler = sampler self._atexit_handler = None @@ -756,18 +889,14 @@ def get_tracer( ), ) - @staticmethod - def get_current_span() -> Span: - return context_api.get_value(SPAN_KEY) # type: ignore - def add_span_processor(self, span_processor: SpanProcessor) -> None: """Registers a new :class:`SpanProcessor` for this `TracerProvider`. The span processors are invoked in the same order they are registered. """ - # no lock here because MultiSpanProcessor.add_span_processor is - # thread safe + # no lock here because add_span_processor is thread safe for both + # SynchronousMultiSpanProcessor and ConcurrentMultiSpanProcessor. self._active_span_processor.add_span_processor(span_processor) def shutdown(self): @@ -776,3 +905,23 @@ def shutdown(self): if self._atexit_handler is not None: atexit.unregister(self._atexit_handler) self._atexit_handler = None + + def force_flush(self, timeout_millis: int = 30000) -> bool: + """Requests the active span processor to process all spans that have not + yet been processed. + + By default force flush is called sequentially on all added span + processors. This means that span processors further back in the list + have less time to flush their spans. + To have span processors flush their spans in parallel it is possible to + initialize the tracer provider with an instance of + `ConcurrentMultiSpanProcessor` at the cost of using multiple threads. + + Args: + timeout_millis: The maximum amount of time to wait for spans to be + processed. + + Returns: + False if the timeout is exceeded, True otherwise. + """ + return self._active_span_processor.force_flush(timeout_millis) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/propagation/b3_format.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/propagation/b3_format.py index c0a080e631..3a9722bc36 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/propagation/b3_format.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/propagation/b3_format.py @@ -16,10 +16,6 @@ import opentelemetry.trace as trace from opentelemetry.context import Context -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, -) from opentelemetry.trace.propagation.httptextformat import ( Getter, HTTPTextFormat, @@ -72,7 +68,7 @@ def extract( elif len(fields) == 4: trace_id, span_id, sampled, _ = fields else: - return set_span_in_context(trace.INVALID_SPAN) + return trace.set_span_in_context(trace.INVALID_SPAN) else: trace_id = ( _extract_first_element( @@ -106,7 +102,7 @@ def extract( # header is set to allow. if sampled in self._SAMPLE_PROPAGATE_VALUES or flags == "1": options |= trace.TraceFlags.SAMPLED - return set_span_in_context( + return trace.set_span_in_context( trace.DefaultSpan( trace.SpanContext( # trace an span ids are encoded in hex, so must be converted @@ -125,7 +121,11 @@ def inject( carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: - span = get_span_from_context(context=context) + span = trace.get_current_span(context=context) + + if span is None: + return + sampled = (trace.TraceFlags.SAMPLED & span.context.trace_flags) != 0 set_in_carrier( carrier, self.TRACE_ID_KEY, format_trace_id(span.context.trace_id), diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/version.py b/opentelemetry-sdk/src/opentelemetry/sdk/version.py index 603bf0b7e5..6d4fefa599 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/version.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/opentelemetry-sdk/tests/metrics/export/test_export.py b/opentelemetry-sdk/tests/metrics/export/test_export.py index c77a132459..2f251f4976 100644 --- a/opentelemetry-sdk/tests/metrics/export/test_export.py +++ b/opentelemetry-sdk/tests/metrics/export/test_export.py @@ -24,9 +24,9 @@ MetricRecord, ) from opentelemetry.sdk.metrics.export.aggregate import ( - CounterAggregator, MinMaxSumCountAggregator, - ObserverAggregator, + SumAggregator, + ValueObserverAggregator, ) from opentelemetry.sdk.metrics.export.batcher import UngroupedBatcher from opentelemetry.sdk.metrics.export.controller import PushController @@ -47,8 +47,8 @@ def test_export(self): ("environment",), ) labels = {"environment": "staging"} - aggregator = CounterAggregator() - record = MetricRecord(aggregator, labels, metric) + aggregator = SumAggregator() + record = MetricRecord(metric, labels, aggregator) result = '{}(data="{}", labels="{}", value={})'.format( ConsoleMetricsExporter.__name__, metric, @@ -62,10 +62,16 @@ def test_export(self): class TestBatcher(unittest.TestCase): def test_aggregator_for_counter(self): + batcher = UngroupedBatcher(True) + self.assertTrue( + isinstance(batcher.aggregator_for(metrics.Counter), SumAggregator) + ) + + def test_aggregator_for_updowncounter(self): batcher = UngroupedBatcher(True) self.assertTrue( isinstance( - batcher.aggregator_for(metrics.Counter), CounterAggregator + batcher.aggregator_for(metrics.UpDownCounter), SumAggregator, ) ) @@ -74,7 +80,7 @@ def test_aggregator_for_counter(self): def test_checkpoint_set(self): meter = metrics.MeterProvider().get_meter(__name__) batcher = UngroupedBatcher(True) - aggregator = CounterAggregator() + aggregator = SumAggregator() metric = metrics.Counter( "available memory", "available memory", @@ -90,7 +96,7 @@ def test_checkpoint_set(self): batcher._batch_map = _batch_map records = batcher.checkpoint_set() self.assertEqual(len(records), 1) - self.assertEqual(records[0].metric, metric) + self.assertEqual(records[0].instrument, metric) self.assertEqual(records[0].labels, labels) self.assertEqual(records[0].aggregator, aggregator) @@ -102,7 +108,7 @@ def test_checkpoint_set_empty(self): def test_finished_collection_stateless(self): meter = metrics.MeterProvider().get_meter(__name__) batcher = UngroupedBatcher(False) - aggregator = CounterAggregator() + aggregator = SumAggregator() metric = metrics.Counter( "available memory", "available memory", @@ -122,7 +128,7 @@ def test_finished_collection_stateless(self): def test_finished_collection_stateful(self): meter = metrics.MeterProvider().get_meter(__name__) batcher = UngroupedBatcher(True) - aggregator = CounterAggregator() + aggregator = SumAggregator() metric = metrics.Counter( "available memory", "available memory", @@ -143,8 +149,8 @@ def test_finished_collection_stateful(self): def test_ungrouped_batcher_process_exists(self): meter = metrics.MeterProvider().get_meter(__name__) batcher = UngroupedBatcher(True) - aggregator = CounterAggregator() - aggregator2 = CounterAggregator() + aggregator = SumAggregator() + aggregator2 = SumAggregator() metric = metrics.Counter( "available memory", "available memory", @@ -170,7 +176,7 @@ def test_ungrouped_batcher_process_exists(self): def test_ungrouped_batcher_process_not_exists(self): meter = metrics.MeterProvider().get_meter(__name__) batcher = UngroupedBatcher(True) - aggregator = CounterAggregator() + aggregator = SumAggregator() metric = metrics.Counter( "available memory", "available memory", @@ -195,7 +201,7 @@ def test_ungrouped_batcher_process_not_exists(self): def test_ungrouped_batcher_process_not_stateful(self): meter = metrics.MeterProvider().get_meter(__name__) batcher = UngroupedBatcher(True) - aggregator = CounterAggregator() + aggregator = SumAggregator() metric = metrics.Counter( "available memory", "available memory", @@ -218,67 +224,67 @@ def test_ungrouped_batcher_process_not_stateful(self): ) -class TestCounterAggregator(unittest.TestCase): +class TestSumAggregator(unittest.TestCase): @staticmethod - def call_update(counter): + def call_update(sum_agg): update_total = 0 for _ in range(0, 100000): val = random.getrandbits(32) - counter.update(val) + sum_agg.update(val) update_total += val return update_total @mock.patch("opentelemetry.sdk.metrics.export.aggregate.time_ns") def test_update(self, time_mock): time_mock.return_value = 123 - counter = CounterAggregator() - counter.update(1.0) - counter.update(2.0) - self.assertEqual(counter.current, 3.0) - self.assertEqual(counter.last_update_timestamp, 123) + sum_agg = SumAggregator() + sum_agg.update(1.0) + sum_agg.update(2.0) + self.assertEqual(sum_agg.current, 3.0) + self.assertEqual(sum_agg.last_update_timestamp, 123) def test_checkpoint(self): - counter = CounterAggregator() - counter.update(2.0) - counter.take_checkpoint() - self.assertEqual(counter.current, 0) - self.assertEqual(counter.checkpoint, 2.0) + sum_agg = SumAggregator() + sum_agg.update(2.0) + sum_agg.take_checkpoint() + self.assertEqual(sum_agg.current, 0) + self.assertEqual(sum_agg.checkpoint, 2.0) def test_merge(self): - counter = CounterAggregator() - counter2 = CounterAggregator() - counter.checkpoint = 1.0 - counter2.checkpoint = 3.0 - counter2.last_update_timestamp = 123 - counter.merge(counter2) - self.assertEqual(counter.checkpoint, 4.0) - self.assertEqual(counter.last_update_timestamp, 123) + sum_agg = SumAggregator() + sum_agg2 = SumAggregator() + sum_agg.checkpoint = 1.0 + sum_agg2.checkpoint = 3.0 + sum_agg2.last_update_timestamp = 123 + sum_agg.merge(sum_agg2) + self.assertEqual(sum_agg.checkpoint, 4.0) + self.assertEqual(sum_agg.last_update_timestamp, 123) def test_concurrent_update(self): - counter = CounterAggregator() + sum_agg = SumAggregator() with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: - fut1 = executor.submit(self.call_update, counter) - fut2 = executor.submit(self.call_update, counter) + fut1 = executor.submit(self.call_update, sum_agg) + fut2 = executor.submit(self.call_update, sum_agg) updapte_total = fut1.result() + fut2.result() - counter.take_checkpoint() - self.assertEqual(updapte_total, counter.checkpoint) + sum_agg.take_checkpoint() + self.assertEqual(updapte_total, sum_agg.checkpoint) def test_concurrent_update_and_checkpoint(self): - counter = CounterAggregator() + sum_agg = SumAggregator() checkpoint_total = 0 with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: - fut = executor.submit(self.call_update, counter) + fut = executor.submit(self.call_update, sum_agg) while not fut.done(): - counter.take_checkpoint() - checkpoint_total += counter.checkpoint + sum_agg.take_checkpoint() + checkpoint_total += sum_agg.checkpoint - counter.take_checkpoint() - checkpoint_total += counter.checkpoint + sum_agg.take_checkpoint() + checkpoint_total += sum_agg.checkpoint self.assertEqual(fut.result(), checkpoint_total) @@ -432,11 +438,11 @@ def test_concurrent_update_and_checkpoint(self): self.assertEqual(checkpoint_total, fut.result()) -class TestObserverAggregator(unittest.TestCase): +class TestValueObserverAggregator(unittest.TestCase): @mock.patch("opentelemetry.sdk.metrics.export.aggregate.time_ns") def test_update(self, time_mock): time_mock.return_value = 123 - observer = ObserverAggregator() + observer = ValueObserverAggregator() # test current values without any update self.assertEqual(observer.mmsc.current, (None, None, None, 0)) self.assertIsNone(observer.current) @@ -455,7 +461,7 @@ def test_update(self, time_mock): self.assertEqual(observer.current, values[-1]) def test_checkpoint(self): - observer = ObserverAggregator() + observer = ValueObserverAggregator() # take checkpoint wihtout any update observer.take_checkpoint() @@ -473,15 +479,19 @@ def test_checkpoint(self): ) def test_merge(self): - observer1 = ObserverAggregator() - observer2 = ObserverAggregator() + observer1 = ValueObserverAggregator() + observer2 = ValueObserverAggregator() mmsc_checkpoint1 = MinMaxSumCountAggregator._TYPE(3, 150, 101, 3) mmsc_checkpoint2 = MinMaxSumCountAggregator._TYPE(1, 33, 44, 2) - checkpoint1 = ObserverAggregator._TYPE(*(mmsc_checkpoint1 + (23,))) + checkpoint1 = ValueObserverAggregator._TYPE( + *(mmsc_checkpoint1 + (23,)) + ) - checkpoint2 = ObserverAggregator._TYPE(*(mmsc_checkpoint2 + (27,))) + checkpoint2 = ValueObserverAggregator._TYPE( + *(mmsc_checkpoint2 + (27,)) + ) observer1.mmsc.checkpoint = mmsc_checkpoint1 observer2.mmsc.checkpoint = mmsc_checkpoint2 @@ -507,15 +517,19 @@ def test_merge(self): self.assertEqual(observer1.last_update_timestamp, 123) def test_merge_last_updated(self): - observer1 = ObserverAggregator() - observer2 = ObserverAggregator() + observer1 = ValueObserverAggregator() + observer2 = ValueObserverAggregator() mmsc_checkpoint1 = MinMaxSumCountAggregator._TYPE(3, 150, 101, 3) mmsc_checkpoint2 = MinMaxSumCountAggregator._TYPE(1, 33, 44, 2) - checkpoint1 = ObserverAggregator._TYPE(*(mmsc_checkpoint1 + (23,))) + checkpoint1 = ValueObserverAggregator._TYPE( + *(mmsc_checkpoint1 + (23,)) + ) - checkpoint2 = ObserverAggregator._TYPE(*(mmsc_checkpoint2 + (27,))) + checkpoint2 = ValueObserverAggregator._TYPE( + *(mmsc_checkpoint2 + (27,)) + ) observer1.mmsc.checkpoint = mmsc_checkpoint1 observer2.mmsc.checkpoint = mmsc_checkpoint2 @@ -541,15 +555,19 @@ def test_merge_last_updated(self): self.assertEqual(observer1.last_update_timestamp, 123) def test_merge_last_updated_none(self): - observer1 = ObserverAggregator() - observer2 = ObserverAggregator() + observer1 = ValueObserverAggregator() + observer2 = ValueObserverAggregator() mmsc_checkpoint1 = MinMaxSumCountAggregator._TYPE(3, 150, 101, 3) mmsc_checkpoint2 = MinMaxSumCountAggregator._TYPE(1, 33, 44, 2) - checkpoint1 = ObserverAggregator._TYPE(*(mmsc_checkpoint1 + (23,))) + checkpoint1 = ValueObserverAggregator._TYPE( + *(mmsc_checkpoint1 + (23,)) + ) - checkpoint2 = ObserverAggregator._TYPE(*(mmsc_checkpoint2 + (27,))) + checkpoint2 = ValueObserverAggregator._TYPE( + *(mmsc_checkpoint2 + (27,)) + ) observer1.mmsc.checkpoint = mmsc_checkpoint1 observer2.mmsc.checkpoint = mmsc_checkpoint2 @@ -575,11 +593,13 @@ def test_merge_last_updated_none(self): self.assertEqual(observer1.last_update_timestamp, 100) def test_merge_with_empty(self): - observer1 = ObserverAggregator() - observer2 = ObserverAggregator() + observer1 = ValueObserverAggregator() + observer2 = ValueObserverAggregator() mmsc_checkpoint1 = MinMaxSumCountAggregator._TYPE(3, 150, 101, 3) - checkpoint1 = ObserverAggregator._TYPE(*(mmsc_checkpoint1 + (23,))) + checkpoint1 = ValueObserverAggregator._TYPE( + *(mmsc_checkpoint1 + (23,)) + ) observer1.mmsc.checkpoint = mmsc_checkpoint1 observer1.checkpoint = checkpoint1 @@ -600,7 +620,6 @@ def test_push_controller(self): controller.shutdown() self.assertTrue(controller.finished.isSet()) - exporter.shutdown.assert_any_call() # shutdown should flush the meter self.assertEqual(meter.collect.call_count, 1) diff --git a/opentelemetry-sdk/tests/metrics/test_metrics.py b/opentelemetry-sdk/tests/metrics/test_metrics.py index 9d5d2b15d8..ae07c23341 100644 --- a/opentelemetry-sdk/tests/metrics/test_metrics.py +++ b/opentelemetry-sdk/tests/metrics/test_metrics.py @@ -38,6 +38,30 @@ def test_resource_empty(self): # pylint: disable=protected-access self.assertIs(meter.resource, resources._EMPTY_RESOURCE) + def test_start_pipeline(self): + exporter = mock.Mock() + meter_provider = metrics.MeterProvider() + meter = meter_provider.get_meter(__name__) + # pylint: disable=protected-access + meter_provider.start_pipeline(meter, exporter, 6) + try: + self.assertEqual(len(meter_provider._exporters), 1) + self.assertEqual(len(meter_provider._controllers), 1) + finally: + meter_provider.shutdown() + + def test_shutdown(self): + controller = mock.Mock() + exporter = mock.Mock() + meter_provider = metrics.MeterProvider() + # pylint: disable=protected-access + meter_provider._controllers = [controller] + meter_provider._exporters = [exporter] + meter_provider.shutdown() + self.assertEqual(controller.shutdown.call_count, 1) + self.assertEqual(exporter.shutdown.call_count, 1) + self.assertIsNone(meter_provider._atexit_handler) + class TestMeter(unittest.TestCase): def test_extends_api(self): @@ -88,7 +112,7 @@ def callback(observer): self.assertIsInstance(observer, metrics_api.Observer) observer.observe(45, {}) - observer = metrics.Observer( + observer = metrics.ValueObserver( callback, "name", "desc", "unit", int, meter, (), True ) @@ -150,6 +174,15 @@ def test_create_metric(self): self.assertEqual(counter.name, "name") self.assertIs(counter.meter.resource, resource) + def test_create_updowncounter(self): + meter = metrics.MeterProvider().get_meter(__name__) + updowncounter = meter.create_metric( + "name", "desc", "unit", float, metrics.UpDownCounter, () + ) + self.assertIsInstance(updowncounter, metrics.UpDownCounter) + self.assertEqual(updowncounter.value_type, float) + self.assertEqual(updowncounter.name, "name") + def test_create_valuerecorder(self): meter = metrics.MeterProvider().get_meter(__name__) valuerecorder = meter.create_metric( @@ -165,7 +198,7 @@ def test_register_observer(self): callback = mock.Mock() observer = meter.register_observer( - callback, "name", "desc", "unit", int, (), True + callback, "name", "desc", "unit", int, metrics.ValueObserver ) self.assertIsInstance(observer, metrics_api.Observer) @@ -185,7 +218,7 @@ def test_unregister_observer(self): callback = mock.Mock() observer = meter.register_observer( - callback, "name", "desc", "unit", int, (), True + callback, "name", "desc", "unit", int, metrics.ValueObserver ) meter.unregister_observer(observer) @@ -272,6 +305,53 @@ def test_add(self): metric.add(2, labels) self.assertEqual(bound_counter.aggregator.current, 5) + @mock.patch("opentelemetry.sdk.metrics.logger") + def test_add_non_decreasing_int_error(self, logger_mock): + meter = metrics.MeterProvider().get_meter(__name__) + metric = metrics.Counter("name", "desc", "unit", int, meter, ("key",)) + labels = {"key": "value"} + bound_counter = metric.bind(labels) + metric.add(3, labels) + metric.add(0, labels) + metric.add(-1, labels) + self.assertEqual(bound_counter.aggregator.current, 3) + self.assertEqual(logger_mock.warning.call_count, 1) + + @mock.patch("opentelemetry.sdk.metrics.logger") + def test_add_non_decreasing_float_error(self, logger_mock): + meter = metrics.MeterProvider().get_meter(__name__) + metric = metrics.Counter( + "name", "desc", "unit", float, meter, ("key",) + ) + labels = {"key": "value"} + bound_counter = metric.bind(labels) + metric.add(3.3, labels) + metric.add(0.0, labels) + metric.add(0.1, labels) + metric.add(-0.1, labels) + self.assertEqual(bound_counter.aggregator.current, 3.4) + self.assertEqual(logger_mock.warning.call_count, 1) + + +class TestUpDownCounter(unittest.TestCase): + @mock.patch("opentelemetry.sdk.metrics.logger") + def test_add(self, logger_mock): + meter = metrics.MeterProvider().get_meter(__name__) + metric = metrics.UpDownCounter( + "name", "desc", "unit", int, meter, ("key",) + ) + labels = {"key": "value"} + bound_counter = metric.bind(labels) + metric.add(3, labels) + metric.add(2, labels) + self.assertEqual(bound_counter.aggregator.current, 5) + + metric.add(0, labels) + metric.add(-3, labels) + metric.add(-1, labels) + self.assertEqual(bound_counter.aggregator.current, 1) + self.assertEqual(logger_mock.warning.call_count, 0) + class TestValueRecorder(unittest.TestCase): def test_record(self): @@ -290,10 +370,142 @@ def test_record(self): ) -class TestObserver(unittest.TestCase): +class TestSumObserver(unittest.TestCase): + def test_observe(self): + meter = metrics.MeterProvider().get_meter(__name__) + observer = metrics.SumObserver( + None, "name", "desc", "unit", int, meter, ("key",), True + ) + labels = {"key": "value"} + key_labels = tuple(sorted(labels.items())) + values = (37, 42, 60, 100) + for val in values: + observer.observe(val, labels) + + self.assertEqual(observer.aggregators[key_labels].current, values[-1]) + + def test_observe_disabled(self): + meter = metrics.MeterProvider().get_meter(__name__) + observer = metrics.SumObserver( + None, "name", "desc", "unit", int, meter, ("key",), False + ) + labels = {"key": "value"} + observer.observe(37, labels) + self.assertEqual(len(observer.aggregators), 0) + + @mock.patch("opentelemetry.sdk.metrics.logger") + def test_observe_incorrect_type(self, logger_mock): + meter = metrics.MeterProvider().get_meter(__name__) + observer = metrics.SumObserver( + None, "name", "desc", "unit", int, meter, ("key",), True + ) + labels = {"key": "value"} + observer.observe(37.0, labels) + self.assertEqual(len(observer.aggregators), 0) + self.assertTrue(logger_mock.warning.called) + + @mock.patch("opentelemetry.sdk.metrics.logger") + def test_observe_non_decreasing_error(self, logger_mock): + meter = metrics.MeterProvider().get_meter(__name__) + observer = metrics.SumObserver( + None, "name", "desc", "unit", int, meter, ("key",), True + ) + labels = {"key": "value"} + observer.observe(37, labels) + observer.observe(14, labels) + self.assertEqual(len(observer.aggregators), 1) + self.assertTrue(logger_mock.warning.called) + + def test_run(self): + meter = metrics.MeterProvider().get_meter(__name__) + + callback = mock.Mock() + observer = metrics.SumObserver( + callback, "name", "desc", "unit", int, meter, (), True + ) + + self.assertTrue(observer.run()) + callback.assert_called_once_with(observer) + + @mock.patch("opentelemetry.sdk.metrics.logger") + def test_run_exception(self, logger_mock): + meter = metrics.MeterProvider().get_meter(__name__) + + callback = mock.Mock() + callback.side_effect = Exception("We have a problem!") + + observer = metrics.SumObserver( + callback, "name", "desc", "unit", int, meter, (), True + ) + + self.assertFalse(observer.run()) + self.assertTrue(logger_mock.warning.called) + + +class TestUpDownSumObserver(unittest.TestCase): + def test_observe(self): + meter = metrics.MeterProvider().get_meter(__name__) + observer = metrics.UpDownSumObserver( + None, "name", "desc", "unit", int, meter, ("key",), True + ) + labels = {"key": "value"} + key_labels = tuple(sorted(labels.items())) + values = (37, 42, 14, 30) + for val in values: + observer.observe(val, labels) + + self.assertEqual(observer.aggregators[key_labels].current, values[-1]) + + def test_observe_disabled(self): + meter = metrics.MeterProvider().get_meter(__name__) + observer = metrics.UpDownSumObserver( + None, "name", "desc", "unit", int, meter, ("key",), False + ) + labels = {"key": "value"} + observer.observe(37, labels) + self.assertEqual(len(observer.aggregators), 0) + + @mock.patch("opentelemetry.sdk.metrics.logger") + def test_observe_incorrect_type(self, logger_mock): + meter = metrics.MeterProvider().get_meter(__name__) + observer = metrics.UpDownSumObserver( + None, "name", "desc", "unit", int, meter, ("key",), True + ) + labels = {"key": "value"} + observer.observe(37.0, labels) + self.assertEqual(len(observer.aggregators), 0) + self.assertTrue(logger_mock.warning.called) + + def test_run(self): + meter = metrics.MeterProvider().get_meter(__name__) + + callback = mock.Mock() + observer = metrics.UpDownSumObserver( + callback, "name", "desc", "unit", int, meter, (), True + ) + + self.assertTrue(observer.run()) + callback.assert_called_once_with(observer) + + @mock.patch("opentelemetry.sdk.metrics.logger") + def test_run_exception(self, logger_mock): + meter = metrics.MeterProvider().get_meter(__name__) + + callback = mock.Mock() + callback.side_effect = Exception("We have a problem!") + + observer = metrics.UpDownSumObserver( + callback, "name", "desc", "unit", int, meter, (), True + ) + + self.assertFalse(observer.run()) + self.assertTrue(logger_mock.warning.called) + + +class TestValueObserver(unittest.TestCase): def test_observe(self): meter = metrics.MeterProvider().get_meter(__name__) - observer = metrics.Observer( + observer = metrics.ValueObserver( None, "name", "desc", "unit", int, meter, ("key",), True ) labels = {"key": "value"} @@ -310,7 +522,7 @@ def test_observe(self): def test_observe_disabled(self): meter = metrics.MeterProvider().get_meter(__name__) - observer = metrics.Observer( + observer = metrics.ValueObserver( None, "name", "desc", "unit", int, meter, ("key",), False ) labels = {"key": "value"} @@ -320,7 +532,7 @@ def test_observe_disabled(self): @mock.patch("opentelemetry.sdk.metrics.logger") def test_observe_incorrect_type(self, logger_mock): meter = metrics.MeterProvider().get_meter(__name__) - observer = metrics.Observer( + observer = metrics.ValueObserver( None, "name", "desc", "unit", int, meter, ("key",), True ) labels = {"key": "value"} @@ -332,7 +544,7 @@ def test_run(self): meter = metrics.MeterProvider().get_meter(__name__) callback = mock.Mock() - observer = metrics.Observer( + observer = metrics.ValueObserver( callback, "name", "desc", "unit", int, meter, (), True ) @@ -346,7 +558,7 @@ def test_run_exception(self, logger_mock): callback = mock.Mock() callback.side_effect = Exception("We have a problem!") - observer = metrics.Observer( + observer = metrics.ValueObserver( callback, "name", "desc", "unit", int, meter, (), True ) @@ -356,27 +568,27 @@ def test_run_exception(self, logger_mock): class TestBoundCounter(unittest.TestCase): def test_add(self): - aggregator = export.aggregate.CounterAggregator() + aggregator = export.aggregate.SumAggregator() bound_metric = metrics.BoundCounter(int, True, aggregator) bound_metric.add(3) self.assertEqual(bound_metric.aggregator.current, 3) def test_add_disabled(self): - aggregator = export.aggregate.CounterAggregator() + aggregator = export.aggregate.SumAggregator() bound_counter = metrics.BoundCounter(int, False, aggregator) bound_counter.add(3) self.assertEqual(bound_counter.aggregator.current, 0) @mock.patch("opentelemetry.sdk.metrics.logger") def test_add_incorrect_type(self, logger_mock): - aggregator = export.aggregate.CounterAggregator() + aggregator = export.aggregate.SumAggregator() bound_counter = metrics.BoundCounter(int, True, aggregator) bound_counter.add(3.0) self.assertEqual(bound_counter.aggregator.current, 0) self.assertTrue(logger_mock.warning.called) def test_update(self): - aggregator = export.aggregate.CounterAggregator() + aggregator = export.aggregate.SumAggregator() bound_counter = metrics.BoundCounter(int, True, aggregator) bound_counter.update(4.0) self.assertEqual(bound_counter.aggregator.current, 4.0) diff --git a/opentelemetry-sdk/tests/trace/propagation/test_b3_format.py b/opentelemetry-sdk/tests/trace/propagation/test_b3_format.py index e6c644dcdb..a5bd1baaa4 100644 --- a/opentelemetry-sdk/tests/trace/propagation/test_b3_format.py +++ b/opentelemetry-sdk/tests/trace/propagation/test_b3_format.py @@ -17,10 +17,7 @@ import opentelemetry.sdk.trace as trace import opentelemetry.sdk.trace.propagation.b3_format as b3_format import opentelemetry.trace as trace_api -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, -) +from opentelemetry.context import get_current FORMAT = b3_format.B3Format() @@ -33,7 +30,7 @@ def get_as_list(dict_object, key): def get_child_parent_new_carrier(old_carrier): ctx = FORMAT.extract(get_as_list, old_carrier) - parent_context = get_span_from_context(ctx).get_context() + parent_context = trace_api.get_current_span(ctx).get_context() parent = trace.Span("parent", parent_context) child = trace.Span( @@ -49,7 +46,7 @@ def get_child_parent_new_carrier(old_carrier): ) new_carrier = {} - ctx = set_span_in_context(child) + ctx = trace_api.set_span_in_context(child) FORMAT.inject(dict.__setitem__, new_carrier, context=ctx) return child, parent, new_carrier @@ -233,7 +230,7 @@ def test_invalid_single_header(self): """ carrier = {FORMAT.SINGLE_HEADER_KEY: "0-1-2-3-4-5-6-7"} ctx = FORMAT.extract(get_as_list, carrier) - span_context = get_span_from_context(ctx).get_context() + span_context = trace_api.get_current_span(ctx).get_context() self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) @@ -245,7 +242,7 @@ def test_missing_trace_id(self): } ctx = FORMAT.extract(get_as_list, carrier) - span_context = get_span_from_context(ctx).get_context() + span_context = trace_api.get_current_span(ctx).get_context() self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) def test_missing_span_id(self): @@ -256,5 +253,12 @@ def test_missing_span_id(self): } ctx = FORMAT.extract(get_as_list, carrier) - span_context = get_span_from_context(ctx).get_context() + span_context = trace_api.get_current_span(ctx).get_context() self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID) + + @staticmethod + def test_inject_empty_context(): + """If the current context has no span, don't add headers""" + new_carrier = {} + FORMAT.inject(dict.__setitem__, new_carrier, get_current()) + assert len(new_carrier) == 0 diff --git a/opentelemetry-sdk/tests/trace/test_span_processor.py b/opentelemetry-sdk/tests/trace/test_span_processor.py new file mode 100644 index 0000000000..90b4003ca6 --- /dev/null +++ b/opentelemetry-sdk/tests/trace/test_span_processor.py @@ -0,0 +1,302 @@ +# Copyright The OpenTelemetry Authors +# +# 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 abc +import time +import typing +import unittest +from threading import Event +from unittest import mock + +from opentelemetry import trace as trace_api +from opentelemetry.sdk import trace + + +def span_event_start_fmt(span_processor_name, span_name): + return span_processor_name + ":" + span_name + ":start" + + +def span_event_end_fmt(span_processor_name, span_name): + return span_processor_name + ":" + span_name + ":end" + + +class MySpanProcessor(trace.SpanProcessor): + def __init__(self, name, span_list): + self.name = name + self.span_list = span_list + + def on_start(self, span: "trace.Span") -> None: + self.span_list.append(span_event_start_fmt(self.name, span.name)) + + def on_end(self, span: "trace.Span") -> None: + self.span_list.append(span_event_end_fmt(self.name, span.name)) + + +class TestSpanProcessor(unittest.TestCase): + def test_span_processor(self): + tracer_provider = trace.TracerProvider() + tracer = tracer_provider.get_tracer(__name__) + + spans_calls_list = [] # filled by MySpanProcessor + expected_list = [] # filled by hand + + # Span processors are created but not added to the tracer yet + sp1 = MySpanProcessor("SP1", spans_calls_list) + sp2 = MySpanProcessor("SP2", spans_calls_list) + + with tracer.start_as_current_span("foo"): + with tracer.start_as_current_span("bar"): + with tracer.start_as_current_span("baz"): + pass + + # at this point lists must be empty + self.assertEqual(len(spans_calls_list), 0) + + # add single span processor + tracer_provider.add_span_processor(sp1) + + with tracer.start_as_current_span("foo"): + expected_list.append(span_event_start_fmt("SP1", "foo")) + + with tracer.start_as_current_span("bar"): + expected_list.append(span_event_start_fmt("SP1", "bar")) + + with tracer.start_as_current_span("baz"): + expected_list.append(span_event_start_fmt("SP1", "baz")) + + expected_list.append(span_event_end_fmt("SP1", "baz")) + + expected_list.append(span_event_end_fmt("SP1", "bar")) + + expected_list.append(span_event_end_fmt("SP1", "foo")) + + self.assertListEqual(spans_calls_list, expected_list) + + spans_calls_list.clear() + expected_list.clear() + + # go for multiple span processors + tracer_provider.add_span_processor(sp2) + + with tracer.start_as_current_span("foo"): + expected_list.append(span_event_start_fmt("SP1", "foo")) + expected_list.append(span_event_start_fmt("SP2", "foo")) + + with tracer.start_as_current_span("bar"): + expected_list.append(span_event_start_fmt("SP1", "bar")) + expected_list.append(span_event_start_fmt("SP2", "bar")) + + with tracer.start_as_current_span("baz"): + expected_list.append(span_event_start_fmt("SP1", "baz")) + expected_list.append(span_event_start_fmt("SP2", "baz")) + + expected_list.append(span_event_end_fmt("SP1", "baz")) + expected_list.append(span_event_end_fmt("SP2", "baz")) + + expected_list.append(span_event_end_fmt("SP1", "bar")) + expected_list.append(span_event_end_fmt("SP2", "bar")) + + expected_list.append(span_event_end_fmt("SP1", "foo")) + expected_list.append(span_event_end_fmt("SP2", "foo")) + + # compare if two lists are the same + self.assertListEqual(spans_calls_list, expected_list) + + def test_add_span_processor_after_span_creation(self): + tracer_provider = trace.TracerProvider() + tracer = tracer_provider.get_tracer(__name__) + + spans_calls_list = [] # filled by MySpanProcessor + expected_list = [] # filled by hand + + # Span processors are created but not added to the tracer yet + sp = MySpanProcessor("SP1", spans_calls_list) + + with tracer.start_as_current_span("foo"): + with tracer.start_as_current_span("bar"): + with tracer.start_as_current_span("baz"): + # add span processor after spans have been created + tracer_provider.add_span_processor(sp) + + expected_list.append(span_event_end_fmt("SP1", "baz")) + + expected_list.append(span_event_end_fmt("SP1", "bar")) + + expected_list.append(span_event_end_fmt("SP1", "foo")) + + self.assertListEqual(spans_calls_list, expected_list) + + +class MultiSpanProcessorTestBase(abc.ABC): + @abc.abstractmethod + def create_multi_span_processor( + self, + ) -> typing.Union[ + trace.SynchronousMultiSpanProcessor, trace.ConcurrentMultiSpanProcessor + ]: + pass + + @staticmethod + def create_default_span() -> trace_api.Span: + span_context = trace_api.SpanContext(37, 73, is_remote=False) + return trace_api.DefaultSpan(span_context) + + def test_on_start(self): + multi_processor = self.create_multi_span_processor() + + mocks = [mock.Mock(spec=trace.SpanProcessor) for _ in range(0, 5)] + for mock_processor in mocks: + multi_processor.add_span_processor(mock_processor) + + span = self.create_default_span() + multi_processor.on_start(span) + + for mock_processor in mocks: + mock_processor.on_start.assert_called_once_with(span) + multi_processor.shutdown() + + def test_on_end(self): + multi_processor = self.create_multi_span_processor() + + mocks = [mock.Mock(spec=trace.SpanProcessor) for _ in range(0, 5)] + for mock_processor in mocks: + multi_processor.add_span_processor(mock_processor) + + span = self.create_default_span() + multi_processor.on_end(span) + + for mock_processor in mocks: + mock_processor.on_end.assert_called_once_with(span) + multi_processor.shutdown() + + def test_on_shutdown(self): + multi_processor = self.create_multi_span_processor() + + mocks = [mock.Mock(spec=trace.SpanProcessor) for _ in range(0, 5)] + for mock_processor in mocks: + multi_processor.add_span_processor(mock_processor) + + multi_processor.shutdown() + + for mock_processor in mocks: + mock_processor.shutdown.assert_called_once_with() + + def test_force_flush(self): + multi_processor = self.create_multi_span_processor() + + mocks = [mock.Mock(spec=trace.SpanProcessor) for _ in range(0, 5)] + for mock_processor in mocks: + multi_processor.add_span_processor(mock_processor) + timeout_millis = 100 + + flushed = multi_processor.force_flush(timeout_millis) + + # pylint: disable=no-member + self.assertTrue(flushed) + for mock_processor in mocks: + # pylint: disable=no-member + self.assertEqual(1, mock_processor.force_flush.call_count) + multi_processor.shutdown() + + +class TestSynchronousMultiSpanProcessor( + MultiSpanProcessorTestBase, unittest.TestCase +): + def create_multi_span_processor( + self, + ) -> trace.SynchronousMultiSpanProcessor: + return trace.SynchronousMultiSpanProcessor() + + def test_force_flush_late_by_timeout(self): + multi_processor = trace.SynchronousMultiSpanProcessor() + + def delayed_flush(_): + time.sleep(0.055) + + mock_processor1 = mock.Mock(spec=trace.SpanProcessor) + mock_processor1.force_flush = mock.Mock(side_effect=delayed_flush) + multi_processor.add_span_processor(mock_processor1) + mock_processor2 = mock.Mock(spec=trace.SpanProcessor) + multi_processor.add_span_processor(mock_processor2) + + flushed = multi_processor.force_flush(50) + + self.assertFalse(flushed) + self.assertEqual(1, mock_processor1.force_flush.call_count) + self.assertEqual(0, mock_processor2.force_flush.call_count) + + def test_force_flush_late_by_span_processor(self): + multi_processor = trace.SynchronousMultiSpanProcessor() + + mock_processor1 = mock.Mock(spec=trace.SpanProcessor) + mock_processor1.force_flush = mock.Mock(return_value=False) + multi_processor.add_span_processor(mock_processor1) + mock_processor2 = mock.Mock(spec=trace.SpanProcessor) + multi_processor.add_span_processor(mock_processor2) + + flushed = multi_processor.force_flush(50) + self.assertFalse(flushed) + self.assertEqual(1, mock_processor1.force_flush.call_count) + self.assertEqual(0, mock_processor2.force_flush.call_count) + + +class TestConcurrentMultiSpanProcessor( + MultiSpanProcessorTestBase, unittest.TestCase +): + def create_multi_span_processor( + self, + ) -> trace.ConcurrentMultiSpanProcessor: + return trace.ConcurrentMultiSpanProcessor(3) + + def test_force_flush_late_by_timeout(self): + multi_processor = trace.ConcurrentMultiSpanProcessor(5) + wait_event = Event() + + def delayed_flush(_): + wait_event.wait() + + late_mock = mock.Mock(spec=trace.SpanProcessor) + late_mock.force_flush = mock.Mock(side_effect=delayed_flush) + mocks = [mock.Mock(spec=trace.SpanProcessor) for _ in range(0, 4)] + mocks.insert(0, late_mock) + + for mock_processor in mocks: + multi_processor.add_span_processor(mock_processor) + + flushed = multi_processor.force_flush(timeout_millis=10) + # let the thread executing the late_mock continue + wait_event.set() + + self.assertFalse(flushed) + for mock_processor in mocks: + self.assertEqual(1, mock_processor.force_flush.call_count) + multi_processor.shutdown() + + def test_force_flush_late_by_span_processor(self): + multi_processor = trace.ConcurrentMultiSpanProcessor(5) + + late_mock = mock.Mock(spec=trace.SpanProcessor) + late_mock.force_flush = mock.Mock(return_value=False) + mocks = [mock.Mock(spec=trace.SpanProcessor) for _ in range(0, 4)] + mocks.insert(0, late_mock) + + for mock_processor in mocks: + multi_processor.add_span_processor(mock_processor) + + flushed = multi_processor.force_flush() + + self.assertFalse(flushed) + for mock_processor in mocks: + self.assertEqual(1, mock_processor.force_flush.call_count) + multi_processor.shutdown() diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 32348d87d9..d68d5ca420 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -117,6 +117,17 @@ class TestUseSpanException(Exception): with tracer.use_span(default_span): raise TestUseSpanException() + def test_tracer_provider_accepts_concurrent_multi_span_processor(self): + span_processor = trace.ConcurrentMultiSpanProcessor(2) + tracer_provider = trace.TracerProvider( + active_span_processor=span_processor + ) + + # pylint: disable=protected-access + self.assertEqual( + span_processor, tracer_provider._active_span_processor + ) + class TestTracerSampling(unittest.TestCase): def test_default_sampler(self): @@ -211,26 +222,10 @@ def test_span_processor_for_source(self): span2.span_processor, tracer_provider._active_span_processor ) - def test_get_current_span_multiple_tracers(self): - """In the case where there are multiple tracers, - get_current_span will return the same active span - for both tracers. - """ - tracer_1 = new_tracer() - tracer_2 = new_tracer() - root = tracer_1.start_span("root") - with tracer_1.use_span(root, True): - self.assertIs(tracer_1.get_current_span(), root) - self.assertIs(tracer_2.get_current_span(), root) - - # outside of the loop, both should not reference a span. - self.assertIs(tracer_1.get_current_span(), None) - self.assertIs(tracer_2.get_current_span(), None) - def test_start_span_implicit(self): tracer = new_tracer() - self.assertIsNone(tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) root = tracer.start_span("root") self.assertIsNotNone(root.start_time) @@ -238,7 +233,7 @@ def test_start_span_implicit(self): self.assertEqual(root.kind, trace_api.SpanKind.INTERNAL) with tracer.use_span(root, True): - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) with tracer.start_span( "child", kind=trace_api.SpanKind.CLIENT @@ -265,11 +260,11 @@ def test_start_span_implicit(self): ) # Verify start_span() did not set the current span. - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) self.assertIsNotNone(child.end_time) - self.assertIsNone(tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) self.assertIsNotNone(root.end_time) def test_start_span_explicit(self): @@ -282,7 +277,7 @@ def test_start_span_explicit(self): trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED), ) - self.assertIsNone(tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) root = tracer.start_span("root") self.assertIsNotNone(root.start_time) @@ -290,7 +285,7 @@ def test_start_span_explicit(self): # Test with the implicit root span with tracer.use_span(root, True): - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) with tracer.start_span("stepchild", other_parent) as child: # The child's parent should be the one passed in, @@ -316,30 +311,30 @@ def test_start_span_explicit(self): ) # Verify start_span() did not set the current span. - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) # Verify ending the child did not set the current span. - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) self.assertIsNotNone(child.end_time) def test_start_as_current_span_implicit(self): tracer = new_tracer() - self.assertIsNone(tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) with tracer.start_as_current_span("root") as root: - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) with tracer.start_as_current_span("child") as child: - self.assertIs(tracer.get_current_span(), child) + self.assertIs(trace_api.get_current_span(), child) self.assertIs(child.parent, root.get_context()) # After exiting the child's scope the parent should become the # current span again. - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) self.assertIsNotNone(child.end_time) - self.assertIsNone(tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) self.assertIsNotNone(root.end_time) def test_start_as_current_span_explicit(self): @@ -351,11 +346,11 @@ def test_start_as_current_span_explicit(self): is_remote=False, ) - self.assertIsNone(tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) # Test with the implicit root span with tracer.start_as_current_span("root") as root: - self.assertIs(tracer.get_current_span(), root) + self.assertIs(trace_api.get_current_span(), root) self.assertIsNotNone(root.start_time) self.assertIsNone(root.end_time) @@ -366,14 +361,14 @@ def test_start_as_current_span_explicit(self): # The child should become the current span as usual, but its # parent should be the one passed in, not the # previously-current span. - self.assertIs(tracer.get_current_span(), child) + self.assertIs(trace_api.get_current_span(), child) self.assertNotEqual(child.parent, root) self.assertIs(child.parent, other_parent) # After exiting the child's scope the last span on the stack should # become current, not the child's parent. - self.assertNotEqual(tracer.get_current_span(), other_parent) - self.assertIs(tracer.get_current_span(), root) + self.assertNotEqual(trace_api.get_current_span(), other_parent) + self.assertIs(trace_api.get_current_span(), root) self.assertIsNotNone(child.end_time) def test_explicit_span_resource(self): @@ -557,7 +552,7 @@ def test_sampling_attributes(self): self.assertEqual(root.attributes["attr-in-both"], "decision-attr") def test_events(self): - self.assertIsNone(self.tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) with self.tracer.start_as_current_span("root") as root: # only event name @@ -613,7 +608,7 @@ def event_formatter(): self.assertEqual(root.events[4].timestamp, now) def test_invalid_event_attributes(self): - self.assertIsNone(self.tracer.get_current_span()) + self.assertIsNone(trace_api.get_current_span()) with self.tracer.start_as_current_span("root") as root: root.add_event("event0", {"attr1": True, "attr2": ["hi", False]}) @@ -806,6 +801,20 @@ def error_status_test(context): .start_as_current_span("root") ) + def test_record_error(self): + span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext)) + try: + raise ValueError("invalid") + except ValueError as err: + span.record_error(err) + error_event = span.events[0] + self.assertEqual("error", error_event.name) + self.assertEqual("invalid", error_event.attributes["error.message"]) + self.assertEqual("ValueError", error_event.attributes["error.type"]) + self.assertIn( + "ValueError: invalid", error_event.attributes["error.stack"] + ) + def span_event_start_fmt(span_processor_name, span_name): return span_processor_name + ":" + span_name + ":start" diff --git a/pyproject.toml b/pyproject.toml index 961304074c..5911dc4e9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,12 @@ exclude = ''' ( /( # generated files docs/examples/opentelemetry-example-app/src/opentelemetry_example_app/grpc/gen| - ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/gen + ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/gen| + opentelemetry-proto/src/opentelemetry/proto/collector| + opentelemetry-proto/src/opentelemetry/proto/common| + opentelemetry-proto/src/opentelemetry/proto/metrics| + opentelemetry-proto/src/opentelemetry/proto/resource| + opentelemetry-proto/src/opentelemetry/proto/trace )/ ) ''' diff --git a/scripts/build.sh b/scripts/build.sh index 682276561b..7a27105d76 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -16,7 +16,7 @@ DISTDIR=dist mkdir -p $DISTDIR rm -rf $DISTDIR/* - for d in opentelemetry-api/ opentelemetry-sdk/ opentelemetry-auto-instrumentation/ ext/*/ ; do + for d in opentelemetry-api/ opentelemetry-sdk/ opentelemetry-instrumentation/ opentelemetry-proto/ ext/*/ ; do ( echo "building $d" cd "$d" diff --git a/scripts/check_for_valid_readme.py b/scripts/check_for_valid_readme.py index edf94d9c3e..d555b4fa2f 100644 --- a/scripts/check_for_valid_readme.py +++ b/scripts/check_for_valid_readme.py @@ -10,7 +10,7 @@ def is_valid_rst(path): """Checks if RST can be rendered on PyPI.""" with open(path) as readme_file: markup = readme_file.read() - return readme_renderer.rst.render(markup) is not None + return readme_renderer.rst.render(markup, stream=sys.stderr) is not None def parse_args(): diff --git a/scripts/coverage.sh b/scripts/coverage.sh index 1794cdf01b..a88aca7475 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -37,6 +37,7 @@ cov ext/opentelemetry-ext-requests cov ext/opentelemetry-ext-jaeger cov ext/opentelemetry-ext-opentracing-shim cov ext/opentelemetry-exporter-cloud-trace +cov ext/opentelemetry-exporter-cloud-monitoring cov ext/opentelemetry-ext-wsgi cov ext/opentelemetry-ext-zipkin cov docs/examples/opentelemetry-example-app diff --git a/scripts/eachdist.py b/scripts/eachdist.py index f1c5e18b60..15b9b8edcb 100755 --- a/scripts/eachdist.py +++ b/scripts/eachdist.py @@ -205,6 +205,7 @@ def setup_instparser(instparser): setup_instparser(instparser) instparser.add_argument("--editable", "-e", action="store_true") + instparser.add_argument("--with-test-deps", action="store_true") instparser.add_argument("--with-dev-deps", action="store_true") instparser.add_argument("--eager-upgrades", action="store_true") @@ -214,7 +215,10 @@ def setup_instparser(instparser): ) setup_instparser(devparser) devparser.set_defaults( - editable=True, with_dev_deps=True, eager_upgrades=True + editable=True, + with_dev_deps=True, + eager_upgrades=True, + with_test_deps=True, ) lintparser = subparsers.add_parser( @@ -424,7 +428,16 @@ def install_args(args): check=True, ) - allfmt = "-e 'file://{}'" if args.editable else "'file://{}'" + allfmt = "-e 'file://{}" if args.editable else "'file://{}" + # packages should provide an extra_requires that is named + # 'test', to denote test dependencies. + extras = [] + if args.with_test_deps: + extras.append("test") + if extras: + allfmt += "[{}]".format(",".join(extras)) + # note the trailing single quote, to close the quote opened above. + allfmt += "'" execute_args( parse_subargs( args, diff --git a/tests/util/src/opentelemetry/test/mock_httptextformat.py b/tests/util/src/opentelemetry/test/mock_httptextformat.py index 1d4b1d5d51..76165c3e4b 100644 --- a/tests/util/src/opentelemetry/test/mock_httptextformat.py +++ b/tests/util/src/opentelemetry/test/mock_httptextformat.py @@ -15,11 +15,7 @@ import typing from opentelemetry import trace -from opentelemetry.context import Context -from opentelemetry.trace.propagation import ( - get_span_from_context, - set_span_in_context, -) +from opentelemetry.context import Context, get_current from opentelemetry.trace.propagation.httptextformat import ( Getter, HTTPTextFormat, @@ -28,6 +24,30 @@ ) +class NOOPHTTPTextFormat(HTTPTextFormat): + """A propagator that does not extract nor inject. + + This class is useful for catching edge cases assuming + a SpanContext will always be present. + """ + + def extract( + self, + get_from_carrier: Getter[HTTPTextFormatT], + carrier: HTTPTextFormatT, + context: typing.Optional[Context] = None, + ) -> Context: + return get_current() + + def inject( + self, + set_in_carrier: Setter[HTTPTextFormatT], + carrier: HTTPTextFormatT, + context: typing.Optional[Context] = None, + ) -> None: + return None + + class MockHTTPTextFormat(HTTPTextFormat): """Mock propagator for testing purposes.""" @@ -44,9 +64,9 @@ def extract( span_id_list = get_from_carrier(carrier, self.SPAN_ID_KEY) if not trace_id_list or not span_id_list: - return set_span_in_context(trace.INVALID_SPAN) + return trace.set_span_in_context(trace.INVALID_SPAN) - return set_span_in_context( + return trace.set_span_in_context( trace.DefaultSpan( trace.SpanContext( trace_id=int(trace_id_list[0]), @@ -62,7 +82,7 @@ def inject( carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: - span = get_span_from_context(context) + span = trace.get_current_span(context) set_in_carrier( carrier, self.TRACE_ID_KEY, str(span.get_context().trace_id) ) diff --git a/tests/util/src/opentelemetry/test/version.py b/tests/util/src/opentelemetry/test/version.py index 962d67c24b..804c927ded 100644 --- a/tests/util/src/opentelemetry/test/version.py +++ b/tests/util/src/opentelemetry/test/version.py @@ -1 +1 @@ -__version__ = "0.9.dev0" +__version__ = "0.10.dev0" diff --git a/tox.ini b/tox.ini index 026d01cc0c..875f63fc3f 100644 --- a/tox.ini +++ b/tox.ini @@ -8,13 +8,17 @@ envlist = py3{4,5,6,7,8}-test-api pypy3-test-api + ; opentelemetry-proto + py3{4,5,6,7,8}-test-proto + pypy3-test-proto + ; opentelemetry-sdk py3{4,5,6,7,8}-test-sdk pypy3-test-sdk - ; opentelemetry-auto-instrumentation - py3{4,5,6,7,8}-test-auto-instrumentation - pypy3-test-auto-instrumentation + ; opentelemetry-instrumentation + py3{5,6,7,8}-test-instrumentation-base + pypy3-test-instrumentation-base ; opentelemetry-example-app py3{4,5,6,7,8}-test-example-app @@ -28,6 +32,10 @@ envlist = py3{5,6,7,8}-test-ext-aiohttp-client pypy3-test-ext-aiohttp-client + ; opentelemetry-ext-botocore + py3{6,7,8}-test-ext-botocore + pypy3-test-ext-botocore + ; opentelemetry-ext-django py3{4,5,6,7,8}-test-ext-django pypy3-test-ext-django @@ -48,6 +56,11 @@ envlist = py3{4,5,6,7,8}-test-ext-requests pypy3-test-ext-requests + ; opentelemetry-instrumentation-starlette. + ; starlette only supports 3.6 and above. + py3{6,7,8}-test-instrumentation-starlette + pypy3-test-instrumentation-starlette + ; opentelemetry-ext-jinja2 py3{4,5,6,7,8}-test-ext-jinja2 pypy3-test-ext-jinja2 @@ -67,6 +80,10 @@ envlist = py3{4,5,6,7,8}-test-ext-opencensusexporter ; ext-opencensusexporter intentionally excluded from pypy3 + ; opentelemetry-ext-otlp + py3{5,6,7,8}-test-ext-otlp + ; ext-otlp intentionally excluded from pypy3 + ; opentelemetry-ext-prometheus py3{4,5,6,7,8}-test-ext-prometheus pypy3-test-ext-prometheus @@ -75,6 +92,10 @@ envlist = py3{4,5,6,7,8}-test-ext-psycopg2 ; ext-psycopg2 intentionally excluded from pypy3 + ; opentelemetry-ext-pymemcache + py3{4,5,6,7,8}-test-ext-pymemcache + pypy3-test-ext-pymemcache + ; opentelemetry-ext-pymongo py3{4,5,6,7,8}-test-ext-pymongo pypy3-test-ext-pymongo @@ -83,6 +104,10 @@ envlist = py3{4,5,6,7,8}-test-ext-pymysql pypy3-test-ext-pymysql + ; opentelemetry-ext-pyramid + py3{4,5,6,7,8}-test-ext-pyramid + pypy3-test-ext-pyramid + ; opentelemetry-ext-asgi py3{5,6,7,8}-test-ext-asgi pypy3-test-ext-asgi @@ -126,6 +151,12 @@ envlist = ; Coverage is temporarily disabled for pypy3 due to the pytest bug. ; pypy3-coverage + ; opentelemetry-exporter-cloud-monitoring + py3{4,5,6,7,8}-test-exporter-cloud-monitoring + + ; opentelemetry-exporter-cloud-trace + py3{4,5,6,7,8}-test-exporter-cloud-trace + lint py38-tracecontext py38-{mypy,mypyinstalled} @@ -146,7 +177,9 @@ setenv = changedir = test-api: opentelemetry-api/tests test-sdk: opentelemetry-sdk/tests - test-auto-instrumentation: opentelemetry-auto-instrumentation/tests + instrumentation-base: opentelemetry-instrumentation/tests + test-instrumentation-starlette: ext/opentelemetry-instrumentation-starlette/tests + test-proto: opentelemetry-proto/tests test-ext-grpc: ext/opentelemetry-ext-grpc/tests test-ext-aiohttp-client: ext/opentelemetry-ext-aiohttp-client/tests test-ext-requests: ext/opentelemetry-ext-requests/tests @@ -157,16 +190,21 @@ changedir = test-ext-django: ext/opentelemetry-ext-django/tests test-ext-mysql: ext/opentelemetry-ext-mysql/tests test-ext-opencensusexporter: ext/opentelemetry-ext-opencensusexporter/tests + test-ext-otlp: ext/opentelemetry-ext-otlp/tests test-ext-prometheus: ext/opentelemetry-ext-prometheus/tests + test-ext-pymemcache: ext/opentelemetry-ext-pymemcache/tests test-ext-pymongo: ext/opentelemetry-ext-pymongo/tests test-exporter-cloud-trace: ext/opentelemetry-exporter-cloud-trace/tests + test-exporter-cloud-monitoring: ext/opentelemetry-exporter-cloud-monitoring/tests test-ext-psycopg2: ext/opentelemetry-ext-psycopg2/tests test-ext-pymysql: ext/opentelemetry-ext-pymysql/tests + test-ext-pyramid: ext/opentelemetry-ext-pyramid/tests test-ext-asgi: ext/opentelemetry-ext-asgi/tests test-ext-sqlite3: ext/opentelemetry-ext-sqlite3/tests test-ext-wsgi: ext/opentelemetry-ext-wsgi/tests test-ext-zipkin: ext/opentelemetry-ext-zipkin/tests test-ext-boto: ext/opentelemetry-ext-boto/tests + test-ext-botocore: ext/opentelemetry-ext-botocore/tests test-ext-flask: ext/opentelemetry-ext-flask/tests test-example-app: docs/examples/opentelemetry-example-app/tests test-getting-started: docs/getting_started/tests @@ -182,47 +220,58 @@ commands_pre = ; cases but it saves a lot of boilerplate in this file. test: pip install {toxinidir}/opentelemetry-api {toxinidir}/opentelemetry-sdk {toxinidir}/tests/util - test-auto-instrumentation: pip install {toxinidir}/opentelemetry-auto-instrumentation + test-proto: pip install {toxinidir}/opentelemetry-proto + ext,instrumentation: pip install {toxinidir}/opentelemetry-instrumentation - example-app: pip install {toxinidir}/opentelemetry-auto-instrumentation {toxinidir}/ext/opentelemetry-ext-requests {toxinidir}/ext/opentelemetry-ext-wsgi {toxinidir}/ext/opentelemetry-ext-flask {toxinidir}/docs/examples/opentelemetry-example-app + example-app: pip install {toxinidir}/opentelemetry-instrumentation {toxinidir}/ext/opentelemetry-ext-requests {toxinidir}/ext/opentelemetry-ext-wsgi {toxinidir}/ext/opentelemetry-ext-flask {toxinidir}/docs/examples/opentelemetry-example-app - getting-started: pip install -e {toxinidir}/opentelemetry-auto-instrumentation -e {toxinidir}/ext/opentelemetry-ext-requests -e {toxinidir}/ext/opentelemetry-ext-wsgi -e {toxinidir}/ext/opentelemetry-ext-flask + getting-started: pip install -e {toxinidir}/opentelemetry-instrumentation -e {toxinidir}/ext/opentelemetry-ext-requests -e {toxinidir}/ext/opentelemetry-ext-wsgi -e {toxinidir}/ext/opentelemetry-ext-flask grpc: pip install {toxinidir}/ext/opentelemetry-ext-grpc[test] - wsgi,flask,django,asgi: pip install {toxinidir}/tests/util - wsgi,flask,django: pip install {toxinidir}/ext/opentelemetry-ext-wsgi - flask,django: pip install {toxinidir}/opentelemetry-auto-instrumentation - asgi: pip install {toxinidir}/ext/opentelemetry-ext-asgi + wsgi,flask,django,asgi,pyramid,starlette: pip install {toxinidir}/tests/util + wsgi,flask,django,pyramid: pip install {toxinidir}/ext/opentelemetry-ext-wsgi + asgi,starlette: pip install {toxinidir}/ext/opentelemetry-ext-asgi - boto: pip install {toxinidir}/opentelemetry-auto-instrumentation boto: pip install {toxinidir}/ext/opentelemetry-ext-boto[test] flask: pip install {toxinidir}/ext/opentelemetry-ext-flask[test] + botocore: pip install {toxinidir}/opentelemetry-instrumentation + botocore: pip install {toxinidir}/ext/opentelemetry-ext-botocore[test] + dbapi: pip install {toxinidir}/ext/opentelemetry-ext-dbapi[test] django: pip install {toxinidir}/ext/opentelemetry-ext-django[test] - mysql: pip install {toxinidir}/opentelemetry-auto-instrumentation {toxinidir}/ext/opentelemetry-ext-dbapi {toxinidir}/ext/opentelemetry-ext-mysql[test] + mysql: pip install {toxinidir}/ext/opentelemetry-ext-dbapi {toxinidir}/ext/opentelemetry-ext-mysql[test] opencensusexporter: pip install {toxinidir}/ext/opentelemetry-ext-opencensusexporter + otlp: pip install {toxinidir}/opentelemetry-proto + otlp: pip install {toxinidir}/ext/opentelemetry-ext-otlp + prometheus: pip install {toxinidir}/ext/opentelemetry-ext-prometheus - pymongo: pip install {toxinidir}/opentelemetry-auto-instrumentation {toxinidir}/ext/opentelemetry-ext-pymongo[test] + pymemcache: pip install {toxinidir}/ext/opentelemetry-ext-pymemcache[test] + + pymongo: pip install {toxinidir}/ext/opentelemetry-ext-pymongo[test] + + psycopg2: pip install {toxinidir}/ext/opentelemetry-ext-dbapi {toxinidir}/ext/opentelemetry-ext-psycopg2 {toxinidir}/ext/opentelemetry-ext-psycopg2[test] - psycopg2: pip install {toxinidir}/opentelemetry-auto-instrumentation {toxinidir}/ext/opentelemetry-ext-dbapi {toxinidir}/ext/opentelemetry-ext-psycopg2 {toxinidir}/ext/opentelemetry-ext-psycopg2[test] + pymysql: pip install {toxinidir}/ext/opentelemetry-ext-dbapi {toxinidir}/ext/opentelemetry-ext-pymysql[test] - pymysql: pip install {toxinidir}/opentelemetry-auto-instrumentation {toxinidir}/ext/opentelemetry-ext-dbapi {toxinidir}/ext/opentelemetry-ext-pymysql[test] + pyramid: pip install {toxinidir}/ext/opentelemetry-ext-pyramid[test] - sqlite3: pip install {toxinidir}/opentelemetry-auto-instrumentation {toxinidir}/ext/opentelemetry-ext-dbapi {toxinidir}/ext/opentelemetry-ext-sqlite3[test] + sqlite3: pip install {toxinidir}/ext/opentelemetry-ext-dbapi {toxinidir}/ext/opentelemetry-ext-sqlite3[test] - redis: pip install {toxinidir}/opentelemetry-auto-instrumentation {toxinidir}/ext/opentelemetry-ext-redis[test] + redis: pip install {toxinidir}/ext/opentelemetry-ext-redis[test] - requests: pip install {toxinidir}/opentelemetry-auto-instrumentation {toxinidir}/ext/opentelemetry-ext-requests[test] + requests: pip install {toxinidir}/ext/opentelemetry-ext-requests[test] - jinja2: pip install {toxinidir}/opentelemetry-auto-instrumentation {toxinidir}/ext/opentelemetry-ext-jinja2[test] + starlette: pip install {toxinidir}/ext/opentelemetry-instrumentation-starlette[test] + + jinja2: pip install {toxinidir}/ext/opentelemetry-ext-jinja2[test] aiohttp-client: pip install {toxinidir}/opentelemetry-sdk {toxinidir}/ext/opentelemetry-ext-aiohttp-client @@ -232,14 +281,16 @@ commands_pre = opentracing-shim: pip install {toxinidir}/ext/opentelemetry-ext-opentracing-shim datadog: pip install {toxinidir}/opentelemetry-sdk {toxinidir}/ext/opentelemetry-ext-datadog - + zipkin: pip install {toxinidir}/ext/opentelemetry-ext-zipkin - sqlalchemy: pip install {toxinidir}/opentelemetry-auto-instrumentation {toxinidir}/ext/opentelemetry-ext-sqlalchemy + sqlalchemy: pip install {toxinidir}/ext/opentelemetry-ext-sqlalchemy - system-metrics: pip install {toxinidir}/opentelemetry-auto-instrumentation system-metrics: pip install {toxinidir}/ext/opentelemetry-ext-system-metrics[test] + exporter-cloud-monitoring: pip install {toxinidir}/ext/opentelemetry-exporter-cloud-monitoring + exporter-cloud-trace: pip install {toxinidir}/ext/opentelemetry-exporter-cloud-trace + ; In order to get a healthy coverage report, ; we have to install packages in editable mode. coverage: python {toxinidir}/scripts/eachdist.py install --editable @@ -278,7 +329,7 @@ deps = httpretty commands_pre = - python scripts/eachdist.py install --editable + python scripts/eachdist.py install --editable --with-test-deps commands = python scripts/eachdist.py lint --check-only @@ -304,9 +355,8 @@ deps = commands_pre = pip install -e {toxinidir}/opentelemetry-api \ - -e {toxinidir}/opentelemetry-auto-instrumentation \ + -e {toxinidir}/opentelemetry-instrumentation \ -e {toxinidir}/opentelemetry-sdk \ - -e {toxinidir}/opentelemetry-auto-instrumentation \ -e {toxinidir}/ext/opentelemetry-ext-requests \ -e {toxinidir}/ext/opentelemetry-ext-wsgi \ -e {toxinidir}/ext/opentelemetry-ext-flask @@ -331,7 +381,7 @@ changedir = commands_pre = pip install -e {toxinidir}/opentelemetry-api \ -e {toxinidir}/opentelemetry-sdk \ - -e {toxinidir}/opentelemetry-auto-instrumentation \ + -e {toxinidir}/opentelemetry-instrumentation \ -e {toxinidir}/tests/util \ -e {toxinidir}/ext/opentelemetry-ext-dbapi \ -e {toxinidir}/ext/opentelemetry-ext-mysql \