diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 00000000000..3dcf0e5cf62
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,14 @@
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+version: 2
+
+sphinx:
+ configuration: docs/conf.py
+
+build:
+ image: latest
+
+python:
+ version: 3.8
+ install:
+ - requirements: docs-requirements.txt
diff --git a/README.md b/README.md
index 17641dd5707..734a824661e 100644
--- a/README.md
+++ b/README.md
@@ -51,12 +51,12 @@ pip install -e ./ext/opentelemetry-ext-{integration}
```python
from opentelemetry import trace
-from opentelemetry.sdk.trace import TracerSource
+from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter
from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor
-trace.set_preferred_tracer_source_implementation(lambda T: TracerSource())
-trace.tracer_source().add_span_processor(
+trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
+trace.tracer_provider().add_span_processor(
SimpleExportSpanProcessor(ConsoleSpanExporter())
)
tracer = trace.get_tracer(__name__)
@@ -70,12 +70,14 @@ with tracer.start_as_current_span('foo'):
```python
from opentelemetry import metrics
-from opentelemetry.sdk.metrics import Counter, Meter
+from opentelemetry.sdk.metrics import Counter, MeterProvider
from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter
+from opentelemetry.sdk.metrics.export.controller import PushController
-metrics.set_preferred_meter_implementation(lambda T: Meter())
-meter = metrics.meter()
+metrics.set_preferred_meter_provider_implementation(lambda _: MeterProvider())
+meter = metrics.get_meter(__name__)
exporter = ConsoleMetricsExporter()
+controller = PushController(meter, exporter, 5)
counter = meter.create_metric(
"available memory",
@@ -89,9 +91,6 @@ counter = meter.create_metric(
label_values = ("staging",)
counter_handle = counter.get_handle(label_values)
counter_handle.add(100)
-
-exporter.export([(counter, label_values)])
-exporter.shutdown()
```
See the [API documentation](https://open-telemetry.github.io/opentelemetry-python/) for more detail, and the [examples folder](./examples) for a more sample code.
diff --git a/docs-requirements.txt b/docs-requirements.txt
new file mode 100644
index 00000000000..ab952473a9b
--- /dev/null
+++ b/docs-requirements.txt
@@ -0,0 +1,10 @@
+sphinx~=2.4
+sphinx-rtd-theme~=0.4
+sphinx-autodoc-typehints~=1.10.2
+
+# Required by ext packages
+opentracing~=2.2.0
+Deprecated>=1.2.6
+thrift>=0.10.0
+pymongo~=3.1
+flask~=1.0
diff --git a/docs/opentelemetry.sdk.metrics.rst b/docs/opentelemetry.sdk.metrics.rst
index ec8687dd2dc..88612046c8a 100644
--- a/docs/opentelemetry.sdk.metrics.rst
+++ b/docs/opentelemetry.sdk.metrics.rst
@@ -8,6 +8,7 @@ Submodules
opentelemetry.sdk.metrics.export.aggregate
opentelemetry.sdk.metrics.export.batcher
+ opentelemetry.sdk.util.instrumentation
.. automodule:: opentelemetry.sdk.metrics
:members:
diff --git a/docs/opentelemetry.sdk.trace.rst b/docs/opentelemetry.sdk.trace.rst
index 7bb3569fe63..1c0e9b6f61c 100644
--- a/docs/opentelemetry.sdk.trace.rst
+++ b/docs/opentelemetry.sdk.trace.rst
@@ -7,6 +7,7 @@ Submodules
.. toctree::
opentelemetry.sdk.trace.export
+ opentelemetry.sdk.util.instrumentation
.. automodule:: opentelemetry.sdk.trace
:members:
diff --git a/docs/opentelemetry.sdk.util.instrumentation.rst b/docs/opentelemetry.sdk.util.instrumentation.rst
new file mode 100644
index 00000000000..a7d391bcee1
--- /dev/null
+++ b/docs/opentelemetry.sdk.util.instrumentation.rst
@@ -0,0 +1,4 @@
+opentelemetry.sdk.util.instrumentation
+==========================================
+
+.. automodule:: opentelemetry.sdk.util.instrumentation
diff --git a/examples/basic_tracer/README.md b/examples/basic_tracer/README.md
index 4dc0e96bea6..ae9e4ca8959 100644
--- a/examples/basic_tracer/README.md
+++ b/examples/basic_tracer/README.md
@@ -53,6 +53,26 @@ Click on the trace to view its details.
+### Collector
+
+* Start Collector
+
+```sh
+$ pip install docker-compose
+$ cd docker
+$ docker-compose up
+
+* Run the sample
+
+$ pip install opentelemetry-ext-otcollector
+$ # from this directory
+$ EXPORTER=collector python tracer.py
+```
+
+Collector is configured to export to Jaeger, follow Jaeger UI isntructions to find the traces.
+
+
+
## Useful links
- For more information on OpenTelemetry, visit:
- For more information on tracing in Python, visit:
diff --git a/examples/basic_tracer/docker/collector-config.yaml b/examples/basic_tracer/docker/collector-config.yaml
new file mode 100644
index 00000000000..bcf59c58024
--- /dev/null
+++ b/examples/basic_tracer/docker/collector-config.yaml
@@ -0,0 +1,19 @@
+receivers:
+ opencensus:
+ endpoint: "0.0.0.0:55678"
+
+exporters:
+ jaeger_grpc:
+ endpoint: jaeger-all-in-one:14250
+ logging: {}
+
+processors:
+ batch:
+ queued_retry:
+
+service:
+ pipelines:
+ traces:
+ receivers: [opencensus]
+ exporters: [jaeger_grpc, logging]
+ processors: [batch, queued_retry]
diff --git a/examples/basic_tracer/docker/docker-compose.yaml b/examples/basic_tracer/docker/docker-compose.yaml
new file mode 100644
index 00000000000..71d7ccd5a11
--- /dev/null
+++ b/examples/basic_tracer/docker/docker-compose.yaml
@@ -0,0 +1,20 @@
+version: "2"
+services:
+
+ # Collector
+ collector:
+ image: omnition/opentelemetry-collector-contrib:latest
+ command: ["--config=/conf/collector-config.yaml", "--log-level=DEBUG"]
+ volumes:
+ - ./collector-config.yaml:/conf/collector-config.yaml
+ ports:
+ - "55678:55678"
+
+ jaeger-all-in-one:
+ image: jaegertracing/all-in-one:latest
+ ports:
+ - "16686:16686"
+ - "6831:6831/udp"
+ - "6832:6832/udp"
+ - "14268"
+ - "14250"
diff --git a/examples/basic_tracer/tracer.py b/examples/basic_tracer/tracer.py
index a6b33a01b37..a454eab7a96 100755
--- a/examples/basic_tracer/tracer.py
+++ b/examples/basic_tracer/tracer.py
@@ -17,7 +17,7 @@
import os
from opentelemetry import trace
-from opentelemetry.sdk.trace import TracerSource
+from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
BatchExportSpanProcessor,
ConsoleSpanExporter,
@@ -26,17 +26,28 @@
if os.getenv("EXPORTER") == "jaeger":
from opentelemetry.ext.jaeger import JaegerSpanExporter
+ print("Using JaegerSpanExporter")
exporter = JaegerSpanExporter(
service_name="basic-service",
agent_host_name="localhost",
agent_port=6831,
)
+elif os.getenv("EXPORTER") == "collector":
+ from opentelemetry.ext.otcollector.trace_exporter import (
+ CollectorSpanExporter,
+ )
+
+ print("Using CollectorSpanExporter")
+ exporter = CollectorSpanExporter(
+ service_name="basic-service", endpoint="localhost:55678"
+ )
else:
+ print("Using ConsoleSpanExporter")
exporter = ConsoleSpanExporter()
# The preferred tracer implementation must be set, as the opentelemetry-api
# defines the interface with a no-op implementation.
-trace.set_preferred_tracer_source_implementation(lambda T: TracerSource())
+trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
# We tell OpenTelemetry who it is that is creating spans. In this case, we have
# no real name (no setup.py), so we make one up. If we had a version, we would
@@ -46,7 +57,7 @@
# SpanExporter receives the spans and send them to the target location.
span_processor = BatchExportSpanProcessor(exporter)
-trace.tracer_source().add_span_processor(span_processor)
+trace.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/examples/http/server.py b/examples/http/server.py
index 8d1aea1e06a..50bc566b77c 100755
--- a/examples/http/server.py
+++ b/examples/http/server.py
@@ -22,7 +22,7 @@
from opentelemetry import trace
from opentelemetry.ext import http_requests
from opentelemetry.ext.wsgi import OpenTelemetryMiddleware
-from opentelemetry.sdk.trace import TracerSource
+from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
BatchExportSpanProcessor,
ConsoleSpanExporter,
@@ -41,17 +41,17 @@
# The preferred tracer implementation must be set, as the opentelemetry-api
# defines the interface with a no-op implementation.
-trace.set_preferred_tracer_source_implementation(lambda T: TracerSource())
+trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
tracer = trace.get_tracer(__name__)
# SpanExporter receives the spans and send them to the target location.
span_processor = BatchExportSpanProcessor(exporter)
-trace.tracer_source().add_span_processor(span_processor)
+trace.tracer_provider().add_span_processor(span_processor)
# Integrations are the glue that binds the OpenTelemetry API and the
# frameworks and libraries that are used together, automatically creating
# Spans and propagating context as appropriate.
-http_requests.enable(trace.tracer_source())
+http_requests.enable(trace.tracer_provider())
app = flask.Flask(__name__)
app.wsgi_app = OpenTelemetryMiddleware(app.wsgi_app)
diff --git a/examples/http/tracer_client.py b/examples/http/tracer_client.py
index 746608db3b4..6fd0a726a42 100755
--- a/examples/http/tracer_client.py
+++ b/examples/http/tracer_client.py
@@ -20,7 +20,7 @@
from opentelemetry import trace
from opentelemetry.ext import http_requests
-from opentelemetry.sdk.trace import TracerSource
+from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
BatchExportSpanProcessor,
ConsoleSpanExporter,
@@ -39,15 +39,15 @@
# The preferred tracer implementation must be set, as the opentelemetry-api
# defines the interface with a no-op implementation.
-trace.set_preferred_tracer_source_implementation(lambda T: TracerSource())
-tracer_source = trace.tracer_source()
+trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
+tracer_provider = trace.tracer_provider()
# SpanExporter receives the spans and send them to the target location.
span_processor = BatchExportSpanProcessor(exporter)
-tracer_source.add_span_processor(span_processor)
+tracer_provider.add_span_processor(span_processor)
# Integrations are the glue that binds the OpenTelemetry API and the
# frameworks and libraries that are used together, automatically creating
# Spans and propagating context as appropriate.
-http_requests.enable(tracer_source)
+http_requests.enable(tracer_provider)
response = requests.get(url="http://127.0.0.1:5000/")
diff --git a/examples/metrics/observer_example.py b/examples/metrics/observer_example.py
new file mode 100644
index 00000000000..aff25ee476c
--- /dev/null
+++ b/examples/metrics/observer_example.py
@@ -0,0 +1,72 @@
+# Copyright 2020, 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 example shows how the Observer metric instrument can be used to capture
+asynchronous metrics data.
+"""
+import psutil
+
+from opentelemetry import metrics
+from opentelemetry.sdk.metrics import LabelSet, MeterProvider
+from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter
+from opentelemetry.sdk.metrics.export.batcher import UngroupedBatcher
+from opentelemetry.sdk.metrics.export.controller import PushController
+
+# Configure a stateful batcher
+batcher = UngroupedBatcher(stateful=True)
+
+metrics.set_preferred_meter_provider_implementation(lambda _: MeterProvider())
+meter = metrics.get_meter(__name__)
+
+# Exporter to export metrics to the console
+exporter = ConsoleMetricsExporter()
+
+# Configure a push controller
+controller = PushController(meter=meter, exporter=exporter, interval=2)
+
+
+# Callback to gather cpu usage
+def get_cpu_usage_callback(observer):
+ for (number, percent) in enumerate(psutil.cpu_percent(percpu=True)):
+ label_set = meter.get_label_set({"cpu_number": str(number)})
+ observer.observe(percent, label_set)
+
+
+meter.register_observer(
+ callback=get_cpu_usage_callback,
+ name="cpu_percent",
+ description="per-cpu usage",
+ unit="1",
+ value_type=float,
+ label_keys=("cpu_number",),
+)
+
+
+# Callback to gather RAM memory usage
+def get_ram_usage_callback(observer):
+ ram_percent = psutil.virtual_memory().percent
+ observer.observe(ram_percent, LabelSet())
+
+
+meter.register_observer(
+ callback=get_ram_usage_callback,
+ name="ram_percent",
+ description="RAM memory usage",
+ unit="1",
+ value_type=float,
+ label_keys=(),
+)
+
+input("Press a key to finish...\n")
diff --git a/examples/metrics/prometheus.py b/examples/metrics/prometheus.py
index 14f612c6a93..4d30f8abcca 100644
--- a/examples/metrics/prometheus.py
+++ b/examples/metrics/prometheus.py
@@ -21,15 +21,15 @@
from opentelemetry import metrics
from opentelemetry.ext.prometheus import PrometheusMetricsExporter
-from opentelemetry.sdk.metrics import Counter, Meter
+from opentelemetry.sdk.metrics import Counter, MeterProvider
from opentelemetry.sdk.metrics.export.controller import PushController
# Start Prometheus client
start_http_server(port=8000, addr="localhost")
# Meter is responsible for creating and recording metrics
-metrics.set_preferred_meter_implementation(lambda _: Meter())
-meter = metrics.meter()
+metrics.set_preferred_meter_provider_implementation(lambda _: MeterProvider())
+meter = metrics.get_meter(__name__)
# exporter to export metrics to Prometheus
prefix = "MyAppPrefix"
exporter = PrometheusMetricsExporter(prefix)
diff --git a/examples/metrics/record.py b/examples/metrics/record.py
index be68c8083ff..a376b2aafc0 100644
--- a/examples/metrics/record.py
+++ b/examples/metrics/record.py
@@ -1,4 +1,4 @@
-# Copyright 2019, OpenTelemetry Authors
+# Copyright 2020, OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,26 +19,38 @@
import time
from opentelemetry import metrics
-from opentelemetry.sdk.metrics import Counter, Meter
+from opentelemetry.sdk.metrics import Counter, MeterProvider
from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter
from opentelemetry.sdk.metrics.export.controller import PushController
+# The preferred tracer implementation must be set, as the opentelemetry-api
+# defines the interface with a no-op implementation.
+metrics.set_preferred_meter_provider_implementation(lambda _: MeterProvider())
# Meter is responsible for creating and recording metrics
-metrics.set_preferred_meter_implementation(lambda _: Meter())
-meter = metrics.meter()
+meter = metrics.get_meter(__name__)
# exporter to export metrics to the console
exporter = ConsoleMetricsExporter()
# controller collects metrics created from meter and exports it via the
# exporter every interval
-controller = PushController(meter, exporter, 5)
+controller = PushController(meter=meter, exporter=exporter, interval=5)
# Example to show how to record using the meter
counter = meter.create_metric(
- "requests", "number of requests", 1, int, Counter, ("environment",)
+ name="requests",
+ description="number of requests",
+ unit="1",
+ value_type=int,
+ metric_type=Counter,
+ label_keys=("environment",),
)
counter2 = meter.create_metric(
- "clicks", "number of clicks", 1, int, Counter, ("environment",)
+ name="clicks",
+ description="number of clicks",
+ unit="1",
+ value_type=int,
+ metric_type=Counter,
+ label_keys=("environment",),
)
# Labelsets are used to identify key-values that are associated with a specific
diff --git a/examples/metrics/simple_example.py b/examples/metrics/simple_example.py
index 75da80b73ac..2b8f5cfac8b 100644
--- a/examples/metrics/simple_example.py
+++ b/examples/metrics/simple_example.py
@@ -1,4 +1,4 @@
-# Copyright 2019, OpenTelemetry Authors
+# Copyright 2020, OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -23,9 +23,8 @@
import time
from opentelemetry import metrics
-from opentelemetry.sdk.metrics import Counter, Measure, Meter
+from opentelemetry.sdk.metrics import Counter, Measure, MeterProvider
from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter
-from opentelemetry.sdk.metrics.export.batcher import UngroupedBatcher
from opentelemetry.sdk.metrics.export.controller import PushController
batcher_mode = "stateful"
@@ -44,18 +43,15 @@ def usage(argv):
usage(sys.argv)
sys.exit(1)
-# Batcher used to collect all created metrics from meter ready for exporting
-# Pass in True/False to indicate whether the batcher is stateful.
-# True indicates the batcher computes checkpoints from over the process
-# lifetime.
-# False indicates the batcher computes checkpoints which describe the updates
-# of a single collection period (deltas)
-batcher = UngroupedBatcher(batcher_mode == "stateful")
-
-# If a batcher is not provided, a default batcher is used
# Meter is responsible for creating and recording metrics
-metrics.set_preferred_meter_implementation(lambda _: Meter(batcher))
-meter = metrics.meter()
+metrics.set_preferred_meter_provider_implementation(lambda _: MeterProvider())
+
+# Meter's namespace corresponds to the string passed as the first argument Pass
+# in True/False to indicate whether the batcher is stateful. True indicates the
+# batcher computes checkpoints from over the process lifetime. False indicates
+# the batcher computes checkpoints which describe the updates of a single
+# collection period (deltas)
+meter = metrics.get_meter(__name__, batcher_mode == "stateful")
# Exporter to export metrics to the console
exporter = ConsoleMetricsExporter()
@@ -66,15 +62,30 @@ def usage(argv):
# Metric instruments allow to capture measurements
requests_counter = meter.create_metric(
- "requests", "number of requests", 1, int, Counter, ("environment",)
+ name="requests",
+ description="number of requests",
+ unit="1",
+ value_type=int,
+ metric_type=Counter,
+ label_keys=("environment",),
)
clicks_counter = meter.create_metric(
- "clicks", "number of clicks", 1, int, Counter, ("environment",)
+ name="clicks",
+ description="number of clicks",
+ unit="1",
+ value_type=int,
+ metric_type=Counter,
+ label_keys=("environment",),
)
requests_size = meter.create_metric(
- "requests_size", "size of requests", 1, int, Measure, ("environment",)
+ name="requests_size",
+ description="size of requests",
+ unit="1",
+ value_type=int,
+ metric_type=Measure,
+ label_keys=("environment",),
)
# Labelsets are used to identify key-values that are associated with a specific
@@ -86,21 +97,15 @@ def usage(argv):
# Update the metric instruments using the direct calling convention
requests_size.record(100, staging_label_set)
requests_counter.add(25, staging_label_set)
-# Sleep for 5 seconds, exported value should be 25
time.sleep(5)
requests_size.record(5000, staging_label_set)
requests_counter.add(50, staging_label_set)
-# Exported value should be 75
time.sleep(5)
requests_size.record(2, testing_label_set)
requests_counter.add(35, testing_label_set)
-# There should be two exported values 75 and 35, one for each labelset
time.sleep(5)
clicks_counter.add(5, staging_label_set)
-# There should be three exported values, labelsets can be reused for different
-# metrics but will be recorded seperately, 75, 35 and 5
-
time.sleep(5)
diff --git a/examples/opentelemetry-example-app/setup.py b/examples/opentelemetry-example-app/setup.py
index ae614aee33c..637ad084d82 100644
--- a/examples/opentelemetry-example-app/setup.py
+++ b/examples/opentelemetry-example-app/setup.py
@@ -16,7 +16,7 @@
setuptools.setup(
name="opentelemetry-example-app",
- version="0.4.dev0",
+ version="0.5.dev0",
author="OpenTelemetry Authors",
author_email="cncf-opentelemetry-contributors@lists.cncf.io",
classifiers=[
diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py
index 62795751d3b..a33b3b58f4c 100644
--- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py
+++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py
@@ -23,7 +23,7 @@
import opentelemetry.ext.http_requests
from opentelemetry import trace
from opentelemetry.ext.flask import instrument_app
-from opentelemetry.sdk.trace import TracerSource
+from opentelemetry.sdk.trace import TracerProvider
def configure_opentelemetry(flask_app: flask.Flask):
@@ -45,7 +45,9 @@ def configure_opentelemetry(flask_app: flask.Flask):
# The preferred implementation of these objects must be set,
# as the opentelemetry-api defines the interface with a no-op
# implementation.
- trace.set_preferred_tracer_source_implementation(lambda _: TracerSource())
+ trace.set_preferred_tracer_provider_implementation(
+ lambda _: TracerProvider()
+ )
# Next, we need to configure how the values that are used by
# traces and metrics are propagated (such as what specific headers
@@ -53,7 +55,7 @@ def configure_opentelemetry(flask_app: flask.Flask):
# Integrations are the glue that binds the OpenTelemetry API
# and the frameworks and libraries that are used together, automatically
# creating Spans and propagating context as appropriate.
- opentelemetry.ext.http_requests.enable(trace.tracer_source())
+ opentelemetry.ext.http_requests.enable(trace.tracer_provider())
instrument_app(flask_app)
diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/metrics_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/metrics_example.py
deleted file mode 100644
index 2f423619021..00000000000
--- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/metrics_example.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright 2019, 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 module serves as an example for a simple application using metrics
-"""
-
-from opentelemetry import metrics
-from opentelemetry.sdk.metrics import Counter, Meter
-from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter
-from opentelemetry.sdk.metrics.export.batcher import UngroupedBatcher
-from opentelemetry.sdk.metrics.export.controller import PushController
-
-batcher = UngroupedBatcher(True)
-metrics.set_preferred_meter_implementation(lambda _: Meter(batcher))
-meter = metrics.meter()
-counter = meter.create_metric(
- "available memory",
- "available memory",
- "bytes",
- int,
- Counter,
- ("environment",),
-)
-
-label_set = meter.get_label_set({"environment": "staging"})
-
-# Direct metric usage
-counter.add(25, label_set)
-
-# Handle usage
-counter_handle = counter.get_handle(label_set)
-counter_handle.add(100)
-
-# Record batch usage
-meter.record_batch(label_set, [(counter, 50)])
-
-exporter = ConsoleMetricsExporter()
-controller = PushController(meter, exporter, 5)
diff --git a/examples/opentelemetry-example-app/tests/test_flask_example.py b/examples/opentelemetry-example-app/tests/test_flask_example.py
index 69be9e4bfc7..cbefadc5328 100644
--- a/examples/opentelemetry-example-app/tests/test_flask_example.py
+++ b/examples/opentelemetry-example-app/tests/test_flask_example.py
@@ -59,7 +59,7 @@ def test_full_path(self):
"traceparent": "00-{:032x}-{:016x}-{:02x}".format(
trace_id,
trace_sdk.generate_span_id(),
- trace.TraceOptions.SAMPLED,
+ trace.TraceFlags.SAMPLED,
)
},
)
diff --git a/examples/opentracing/main.py b/examples/opentracing/main.py
index 922e1263b50..665099aeeef 100755
--- a/examples/opentracing/main.py
+++ b/examples/opentracing/main.py
@@ -3,13 +3,13 @@
from opentelemetry import trace
from opentelemetry.ext import opentracing_shim
from opentelemetry.ext.jaeger import JaegerSpanExporter
-from opentelemetry.sdk.trace import TracerSource
+from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor
from rediscache import RedisCache
# Configure the tracer using the default implementation
-trace.set_preferred_tracer_source_implementation(lambda T: TracerSource())
-tracer_source = trace.tracer_source()
+trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
+tracer_provider = trace.tracer_provider()
# Configure the tracer to export traces to Jaeger
jaeger_exporter = JaegerSpanExporter(
@@ -18,11 +18,11 @@
agent_port=6831,
)
span_processor = SimpleExportSpanProcessor(jaeger_exporter)
-tracer_source.add_span_processor(span_processor)
+tracer_provider.add_span_processor(span_processor)
# Create an OpenTracing shim. This implements the OpenTracing tracer API, but
# forwards calls to the underlying OpenTelemetry tracer.
-opentracing_tracer = opentracing_shim.create_tracer(tracer_source)
+opentracing_tracer = opentracing_shim.create_tracer(tracer_provider)
# Our example caching library expects an OpenTracing-compliant tracer.
redis_cache = RedisCache(opentracing_tracer)
diff --git a/ext/opentelemetry-ext-dbapi/README.rst b/ext/opentelemetry-ext-dbapi/README.rst
index b0bdbdd3126..895b47b9ba1 100644
--- a/ext/opentelemetry-ext-dbapi/README.rst
+++ b/ext/opentelemetry-ext-dbapi/README.rst
@@ -1,5 +1,5 @@
OpenTelemetry Database API integration
-=================================
+======================================
The trace integration with Database API supports libraries following the specification.
@@ -8,16 +8,16 @@ The trace integration with Database API supports libraries following the specifi
Usage
-----
-.. code:: python
+.. code-block:: python
import mysql.connector
- from opentelemetry.trace import tracer_source
+ from opentelemetry.trace import tracer_provider
from opentelemetry.ext.dbapi import trace_integration
- trace.set_preferred_tracer_source_implementation(lambda T: TracerSource())
+ trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
tracer = trace.get_tracer(__name__)
# Ex: mysql.connector
- trace_integration(tracer_source(), mysql.connector, "connect", "mysql")
+ trace_integration(tracer_provider(), mysql.connector, "connect", "mysql")
References
diff --git a/ext/opentelemetry-ext-dbapi/setup.cfg b/ext/opentelemetry-ext-dbapi/setup.cfg
index f0de68dc263..3826d808934 100644
--- a/ext/opentelemetry-ext-dbapi/setup.cfg
+++ b/ext/opentelemetry-ext-dbapi/setup.cfg
@@ -39,7 +39,7 @@ package_dir=
=src
packages=find_namespace:
install_requires =
- opentelemetry-api >= 0.4.dev0
+ opentelemetry-api >= 0.5.dev0
wrapt >= 1.0.0, < 2.0.0
[options.packages.find]
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 2f792fff802..d13bf967481 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.4.dev0"
+__version__ = "0.5.dev0"
diff --git a/ext/opentelemetry-ext-docker-tests/tests/pymongo/test_pymongo_functional.py b/ext/opentelemetry-ext-docker-tests/tests/pymongo/test_pymongo_functional.py
index 4ef14fd789b..c728aebf380 100644
--- a/ext/opentelemetry-ext-docker-tests/tests/pymongo/test_pymongo_functional.py
+++ b/ext/opentelemetry-ext-docker-tests/tests/pymongo/test_pymongo_functional.py
@@ -20,7 +20,7 @@
from opentelemetry import trace as trace_api
from opentelemetry.ext.pymongo import trace_integration
-from opentelemetry.sdk.trace import Span, Tracer, TracerSource
+from opentelemetry.sdk.trace import Span, Tracer, TracerProvider
from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
InMemorySpanExporter,
@@ -35,11 +35,11 @@
class TestFunctionalPymongo(unittest.TestCase):
@classmethod
def setUpClass(cls):
- cls._tracer_source = TracerSource()
- cls._tracer = Tracer(cls._tracer_source, None)
+ cls._tracer_provider = TracerProvider()
+ cls._tracer = Tracer(cls._tracer_provider, None)
cls._span_exporter = InMemorySpanExporter()
cls._span_processor = SimpleExportSpanProcessor(cls._span_exporter)
- cls._tracer_source.add_span_processor(cls._span_processor)
+ cls._tracer_provider.add_span_processor(cls._span_processor)
trace_integration(cls._tracer)
client = MongoClient(
MONGODB_HOST, MONGODB_PORT, serverSelectionTimeoutMS=2000
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 aa9217c00ea..b30b42d3fd2 100644
--- a/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py
+++ b/ext/opentelemetry-ext-flask/src/opentelemetry/ext/flask/__init__.py
@@ -6,8 +6,9 @@
from flask import request as flask_request
import opentelemetry.ext.wsgi as otel_wsgi
-from opentelemetry import propagators, trace
+from opentelemetry import context, propagators, trace
from opentelemetry.ext.flask.version import __version__
+from opentelemetry.trace.propagation import get_span_from_context
from opentelemetry.util import time_ns
logger = logging.getLogger(__name__)
@@ -15,6 +16,7 @@
_ENVIRON_STARTTIME_KEY = "opentelemetry-flask.starttime_key"
_ENVIRON_SPAN_KEY = "opentelemetry-flask.span_key"
_ENVIRON_ACTIVATION_KEY = "opentelemetry-flask.activation_key"
+_ENVIRON_TOKEN = "opentelemetry-flask.token"
def instrument_app(flask):
@@ -57,8 +59,8 @@ def _before_flask_request():
span_name = flask_request.endpoint or otel_wsgi.get_default_span_name(
environ
)
- parent_span = propagators.extract(
- otel_wsgi.get_header_from_environ, environ
+ token = context.attach(
+ propagators.extract(otel_wsgi.get_header_from_environ, environ)
)
tracer = trace.get_tracer(__name__, __version__)
@@ -69,7 +71,6 @@ def _before_flask_request():
attributes["http.route"] = flask_request.url_rule.rule
span = tracer.start_span(
span_name,
- parent_span,
kind=trace.SpanKind.SERVER,
attributes=attributes,
start_time=environ.get(_ENVIRON_STARTTIME_KEY),
@@ -78,6 +79,7 @@ def _before_flask_request():
activation.__enter__()
environ[_ENVIRON_ACTIVATION_KEY] = activation
environ[_ENVIRON_SPAN_KEY] = span
+ environ[_ENVIRON_TOKEN] = token
def _teardown_flask_request(exc):
@@ -95,3 +97,4 @@ def _teardown_flask_request(exc):
activation.__exit__(
type(exc), exc, getattr(exc, "__traceback__", None)
)
+ context.detach(flask_request.environ.get(_ENVIRON_TOKEN))
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 2f792fff802..d13bf967481 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.4.dev0"
+__version__ = "0.5.dev0"
diff --git a/ext/opentelemetry-ext-http-requests/README.rst b/ext/opentelemetry-ext-http-requests/README.rst
index 7b2d4340238..a4b79005b57 100644
--- a/ext/opentelemetry-ext-http-requests/README.rst
+++ b/ext/opentelemetry-ext-http-requests/README.rst
@@ -22,9 +22,9 @@ Usage
import requests
import opentelemetry.ext.http_requests
- from opentelemetry.trace import tracer_source
+ from opentelemetry.trace import tracer_provider
- opentelemetry.ext.http_requests.enable(tracer_source())
+ opentelemetry.ext.http_requests.enable(tracer_provider())
response = requests.get(url='https://www.example.org/')
Limitations
diff --git a/ext/opentelemetry-ext-http-requests/setup.cfg b/ext/opentelemetry-ext-http-requests/setup.cfg
index bb3f50c4808..a064eb3c627 100644
--- a/ext/opentelemetry-ext-http-requests/setup.cfg
+++ b/ext/opentelemetry-ext-http-requests/setup.cfg
@@ -39,7 +39,7 @@ package_dir=
=src
packages=find_namespace:
install_requires =
- opentelemetry-api >= 0.4.dev0
+ opentelemetry-api >= 0.5.dev0
requests ~= 2.0
[options.packages.find]
diff --git a/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/__init__.py b/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/__init__.py
index a557e6fc453..8e4b3e2cc06 100644
--- a/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/__init__.py
+++ b/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/__init__.py
@@ -32,7 +32,7 @@
# if the SDK/tracer is already using `requests` they may, in theory, bypass our
# instrumentation when using `import from`, etc. (currently we only instrument
# a instance method so the probability for that is very low).
-def enable(tracer_source):
+def enable(tracer_provider):
"""Enables tracing of all requests calls that go through
:code:`requests.session.Session.request` (this includes
:code:`requests.get`, etc.)."""
@@ -47,7 +47,7 @@ def enable(tracer_source):
# Guard against double instrumentation
disable()
- tracer = tracer_source.get_tracer(__name__, __version__)
+ tracer = tracer_provider.get_tracer(__name__, __version__)
wrapped = Session.request
@@ -76,7 +76,7 @@ def instrumented_request(self, method, url, *args, **kwargs):
# to access propagators.
headers = kwargs.setdefault("headers", {})
- propagators.inject(tracer, type(headers).__setitem__, headers)
+ propagators.inject(type(headers).__setitem__, headers)
result = wrapped(self, method, url, *args, **kwargs) # *** PROCEED
span.set_attribute("http.status_code", result.status_code)
diff --git a/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/version.py b/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/version.py
index 2f792fff802..d13bf967481 100644
--- a/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/version.py
+++ b/ext/opentelemetry-ext-http-requests/src/opentelemetry/ext/http_requests/version.py
@@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-__version__ = "0.4.dev0"
+__version__ = "0.5.dev0"
diff --git a/ext/opentelemetry-ext-http-requests/tests/test_requests_integration.py b/ext/opentelemetry-ext-http-requests/tests/test_requests_integration.py
index de659f20e18..ea37cbbf1b6 100644
--- a/ext/opentelemetry-ext-http-requests/tests/test_requests_integration.py
+++ b/ext/opentelemetry-ext-http-requests/tests/test_requests_integration.py
@@ -29,10 +29,10 @@ class TestRequestsIntegration(unittest.TestCase):
# TODO: Copy & paste from test_wsgi_middleware
def setUp(self):
self.span_attrs = {}
- self.tracer_source = trace.DefaultTracerSource()
+ self.tracer_provider = trace.DefaultTracerProvider()
self.tracer = trace.DefaultTracer()
self.get_tracer_patcher = mock.patch.object(
- self.tracer_source,
+ self.tracer_provider,
"get_tracer",
autospec=True,
spec_set=True,
@@ -41,6 +41,7 @@ def setUp(self):
self.get_tracer = self.get_tracer_patcher.start()
self.span_context_manager = mock.MagicMock()
self.span = mock.create_autospec(trace.Span, spec_set=True)
+ self.span.get_context.return_value = trace.INVALID_SPAN_CONTEXT
self.span_context_manager.__enter__.return_value = self.span
def setspanattr(key, value):
@@ -70,7 +71,7 @@ def setspanattr(key, value):
self.start_as_current_span = self.start_span_patcher.start()
self.send = self.send_patcher.start()
- opentelemetry.ext.http_requests.enable(self.tracer_source)
+ opentelemetry.ext.http_requests.enable(self.tracer_provider)
distver = pkg_resources.get_distribution(
"opentelemetry-ext-http-requests"
).version
diff --git a/ext/opentelemetry-ext-jaeger/README.rst b/ext/opentelemetry-ext-jaeger/README.rst
index 00339cb37f8..6813fbdeee8 100644
--- a/ext/opentelemetry-ext-jaeger/README.rst
+++ b/ext/opentelemetry-ext-jaeger/README.rst
@@ -32,10 +32,10 @@ gRPC is still not supported by this implementation.
from opentelemetry import trace
from opentelemetry.ext import jaeger
- from opentelemetry.sdk.trace import TracerSource
+ from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchExportSpanProcessor
- trace.set_preferred_tracer_source_implementation(lambda T: TracerSource())
+ trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
tracer = trace.get_tracer(__name__)
# create a JaegerSpanExporter
@@ -56,7 +56,7 @@ gRPC is still not supported by this implementation.
span_processor = BatchExportSpanProcessor(jaeger_exporter)
# add to the tracer
- tracer.add_span_processor(span_processor)
+ trace.tracer_provider().add_span_processor(span_processor)
with tracer.start_as_current_span('foo'):
print('Hello world!')
diff --git a/ext/opentelemetry-ext-jaeger/examples/jaeger_exporter_example.py b/ext/opentelemetry-ext-jaeger/examples/jaeger_exporter_example.py
index 6b0646bb99d..81815da935c 100644
--- a/ext/opentelemetry-ext-jaeger/examples/jaeger_exporter_example.py
+++ b/ext/opentelemetry-ext-jaeger/examples/jaeger_exporter_example.py
@@ -2,10 +2,10 @@
from opentelemetry import trace
from opentelemetry.ext import jaeger
-from opentelemetry.sdk.trace import TracerSource
+from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchExportSpanProcessor
-trace.set_preferred_tracer_source_implementation(lambda T: TracerSource())
+trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
tracer = trace.get_tracer(__name__)
# create a JaegerSpanExporter
@@ -26,7 +26,7 @@
span_processor = BatchExportSpanProcessor(jaeger_exporter)
# add to the tracer factory
-trace.tracer_source().add_span_processor(span_processor)
+trace.tracer_provider().add_span_processor(span_processor)
# create some spans for testing
with tracer.start_as_current_span("foo") as foo:
diff --git a/ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/__init__.py b/ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/__init__.py
index a90313d9131..6679ce6b7e8 100644
--- a/ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/__init__.py
+++ b/ext/opentelemetry-ext-jaeger/src/opentelemetry/ext/jaeger/__init__.py
@@ -171,7 +171,7 @@ def _translate_to_jaeger(spans: Span):
refs = _extract_refs_from_span(span)
logs = _extract_logs_from_span(span)
- flags = int(ctx.trace_options)
+ flags = int(ctx.trace_flags)
jaeger_span = jaeger.Span(
traceIdHigh=_get_trace_id_high(trace_id),
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 39a7c8a016d..fdf63e3792f 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.4.dev0"
+__version__ = "0.5.dev0"
diff --git a/ext/opentelemetry-ext-mysql/README.rst b/ext/opentelemetry-ext-mysql/README.rst
index e899a980fc8..087d4cb3611 100644
--- a/ext/opentelemetry-ext-mysql/README.rst
+++ b/ext/opentelemetry-ext-mysql/README.rst
@@ -1,10 +1,10 @@
OpenTelemetry MySQL integration
-=================================
+===============================
The integration with MySQL supports the `mysql-connector`_ library and is specified
to ``trace_integration`` using ``'MySQL'``.
-.. mysql-connector: https://pypi.org/project/mysql-connector/
+.. _mysql-connector: https://pypi.org/project/mysql-connector/
Usage
-----
@@ -12,10 +12,10 @@ Usage
.. code:: python
import mysql.connector
- from opentelemetry.trace import tracer_source
+ from opentelemetry.trace import tracer_provider
from opentelemetry.ext.mysql import trace_integration
- trace_integration(tracer_source())
+ trace_integration(tracer_provider())
cnx = mysql.connector.connect(database='MySQL_Database')
cursor = cnx.cursor()
cursor.execute("INSERT INTO test (testField) VALUES (123)"
diff --git a/ext/opentelemetry-ext-mysql/setup.cfg b/ext/opentelemetry-ext-mysql/setup.cfg
index fdc608bb3d1..b29636fa134 100644
--- a/ext/opentelemetry-ext-mysql/setup.cfg
+++ b/ext/opentelemetry-ext-mysql/setup.cfg
@@ -39,7 +39,7 @@ package_dir=
=src
packages=find_namespace:
install_requires =
- opentelemetry-api >= 0.4.dev0
+ opentelemetry-api >= 0.5.dev0
mysql-connector-python ~= 8.0
wrapt >= 1.0.0, < 2.0.0
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 2f792fff802..d13bf967481 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.4.dev0"
+__version__ = "0.5.dev0"
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 11ef52ec796..bd9d22678ed 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
@@ -29,11 +29,11 @@
import time
from opentelemetry import trace
- from opentelemetry.sdk.trace import TracerSource
+ from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.ext.opentracing_shim import create_tracer
# Tell OpenTelemetry which Tracer implementation to use.
- trace.set_preferred_tracer_source_implementation(lambda T: TracerSource())
+ trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
# Create an OpenTelemetry Tracer.
otel_tracer = trace.get_tracer(__name__)
@@ -93,19 +93,23 @@
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,
+)
logger = logging.getLogger(__name__)
-def create_tracer(otel_tracer_source):
+def create_tracer(otel_tracer_provider):
"""Creates a :class:`TracerShim` object from the provided OpenTelemetry
- :class:`opentelemetry.trace.TracerSource`.
+ :class:`opentelemetry.trace.TracerProvider`.
The returned :class:`TracerShim` is an implementation of
:class:`opentracing.Tracer` using OpenTelemetry under the hood.
Args:
- otel_tracer_source: A :class:`opentelemetry.trace.TracerSource` to be
+ otel_tracer_provider: A :class:`opentelemetry.trace.TracerProvider` to be
used for constructing the :class:`TracerShim`. A tracer from this
source will be used to perform the actual tracing when user code is
instrumented using the OpenTracing API.
@@ -114,7 +118,7 @@ def create_tracer(otel_tracer_source):
The created :class:`TracerShim`.
"""
- return TracerShim(otel_tracer_source.get_tracer(__name__, __version__))
+ return TracerShim(otel_tracer_provider.get_tracer(__name__, __version__))
class SpanContextShim(opentracing.SpanContext):
@@ -677,11 +681,8 @@ def inject(self, span_context, format, carrier):
propagator = propagators.get_global_httptextformat()
- propagator.inject(
- DefaultSpan(span_context.unwrap()),
- type(carrier).__setitem__,
- carrier,
- )
+ ctx = set_span_in_context(DefaultSpan(span_context.unwrap()))
+ propagator.inject(type(carrier).__setitem__, carrier, context=ctx)
def extract(self, format, carrier):
"""Implements the ``extract`` method from the base class."""
@@ -700,6 +701,7 @@ def get_as_list(dict_object, key):
return [value] if value is not None else []
propagator = propagators.get_global_httptextformat()
- otel_context = propagator.extract(get_as_list, carrier)
+ ctx = propagator.extract(get_as_list, carrier)
+ otel_context = get_span_from_context(ctx).get_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 2f792fff802..d13bf967481 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.4.dev0"
+__version__ = "0.5.dev0"
diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py
index eacfc639b37..2a3fe819c9b 100644
--- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py
+++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py
@@ -16,15 +16,26 @@
# pylint:disable=no-member
import time
+import typing
from unittest import TestCase
import opentracing
import opentelemetry.ext.opentracing_shim as opentracingshim
from opentelemetry import propagators, trace
-from opentelemetry.context.propagation.httptextformat import HTTPTextFormat
+from opentelemetry.context import Context
from opentelemetry.ext.opentracing_shim import util
-from opentelemetry.sdk.trace import TracerSource
+from opentelemetry.sdk.trace import TracerProvider
+from opentelemetry.trace.propagation import (
+ get_span_from_context,
+ set_span_in_context,
+)
+from opentelemetry.trace.propagation.httptextformat import (
+ Getter,
+ HTTPTextFormat,
+ HTTPTextFormatT,
+ Setter,
+)
class TestShim(TestCase):
@@ -33,7 +44,7 @@ class TestShim(TestCase):
def setUp(self):
"""Create an OpenTelemetry tracer and a shim before every test case."""
- self.shim = opentracingshim.create_tracer(trace.tracer_source())
+ self.shim = opentracingshim.create_tracer(trace.tracer_provider())
@classmethod
def setUpClass(cls):
@@ -41,15 +52,15 @@ def setUpClass(cls):
every test method.
"""
- trace.set_preferred_tracer_source_implementation(
- lambda T: TracerSource()
+ trace.set_preferred_tracer_provider_implementation(
+ lambda T: TracerProvider()
)
# Save current propagator to be restored on teardown.
cls._previous_propagator = propagators.get_global_httptextformat()
# Set mock propagator for testing.
- propagators.set_global_httptextformat(MockHTTPTextFormat)
+ propagators.set_global_httptextformat(MockHTTPTextFormat())
@classmethod
def tearDownClass(cls):
@@ -541,23 +552,37 @@ class MockHTTPTextFormat(HTTPTextFormat):
TRACE_ID_KEY = "mock-traceid"
SPAN_ID_KEY = "mock-spanid"
- @classmethod
- def extract(cls, get_from_carrier, carrier):
- trace_id_list = get_from_carrier(carrier, cls.TRACE_ID_KEY)
- span_id_list = get_from_carrier(carrier, cls.SPAN_ID_KEY)
+ def extract(
+ self,
+ get_from_carrier: Getter[HTTPTextFormatT],
+ carrier: HTTPTextFormatT,
+ context: typing.Optional[Context] = None,
+ ) -> Context:
+ trace_id_list = get_from_carrier(carrier, self.TRACE_ID_KEY)
+ span_id_list = get_from_carrier(carrier, self.SPAN_ID_KEY)
if not trace_id_list or not span_id_list:
- return trace.INVALID_SPAN_CONTEXT
+ return set_span_in_context(trace.INVALID_SPAN)
- return trace.SpanContext(
- trace_id=int(trace_id_list[0]), span_id=int(span_id_list[0])
+ return set_span_in_context(
+ trace.DefaultSpan(
+ trace.SpanContext(
+ trace_id=int(trace_id_list[0]),
+ span_id=int(span_id_list[0]),
+ )
+ )
)
- @classmethod
- def inject(cls, span, set_in_carrier, carrier):
+ def inject(
+ self,
+ set_in_carrier: Setter[HTTPTextFormatT],
+ carrier: HTTPTextFormatT,
+ context: typing.Optional[Context] = None,
+ ) -> None:
+ span = get_span_from_context(context)
set_in_carrier(
- carrier, cls.TRACE_ID_KEY, str(span.get_context().trace_id)
+ carrier, self.TRACE_ID_KEY, str(span.get_context().trace_id)
)
set_in_carrier(
- carrier, cls.SPAN_ID_KEY, str(span.get_context().span_id)
+ carrier, self.SPAN_ID_KEY, str(span.get_context().span_id)
)
diff --git a/ext/opentelemetry-ext-otcollector/CHANGELOG.md b/ext/opentelemetry-ext-otcollector/CHANGELOG.md
new file mode 100644
index 00000000000..617d979ab29
--- /dev/null
+++ b/ext/opentelemetry-ext-otcollector/CHANGELOG.md
@@ -0,0 +1,4 @@
+# Changelog
+
+## Unreleased
+
diff --git a/ext/opentelemetry-ext-otcollector/README.rst b/ext/opentelemetry-ext-otcollector/README.rst
new file mode 100644
index 00000000000..33d8d587479
--- /dev/null
+++ b/ext/opentelemetry-ext-otcollector/README.rst
@@ -0,0 +1,55 @@
+OpenTelemetry Collector Exporter
+================================
+
+|pypi|
+
+.. |pypi| image:: https://badge.fury.io/py/opentelemetry-ext-otcollector.svg
+ :target: https://pypi.org/project/opentelemetry-ext-otcollector/
+
+This library allows to export data to `OpenTelemetry Collector `_.
+
+Installation
+------------
+
+::
+
+ pip install opentelemetry-ext-otcollector
+
+
+Usage
+-----
+
+The **OpenTelemetry Collector Exporter** allows to export `OpenTelemetry`_ traces to `OpenTelemetry Collector`_.
+
+.. code:: python
+
+ from opentelemetry import trace
+ from opentelemetry.ext.otcollector.trace_exporter import CollectorSpanExporter
+ from opentelemetry.sdk.trace import TracerProvider
+ from opentelemetry.sdk.trace.export import BatchExportSpanProcessor
+
+
+ # create a CollectorSpanExporter
+ collector_exporter = CollectorSpanExporter(
+ # optional:
+ # endpoint="myCollectorUrl:55678",
+ # service_name="test_service",
+ # host_name="machine/container name",
+ )
+
+ # Create a BatchExportSpanProcessor and add the exporter to it
+ span_processor = BatchExportSpanProcessor(collector_exporter)
+
+ # Configure the tracer to use the collector exporter
+ tracer_provider = TracerProvider()
+ tracer_provider.add_span_processor(span_processor)
+ tracer = TracerProvider().get_tracer(__name__)
+
+ with tracer.start_as_current_span("foo"):
+ print("Hello world!")
+
+References
+----------
+
+* `OpenTelemetry Collector `_
+* `OpenTelemetry Project `_
diff --git a/ext/opentelemetry-ext-otcollector/setup.cfg b/ext/opentelemetry-ext-otcollector/setup.cfg
new file mode 100644
index 00000000000..acc5b37723c
--- /dev/null
+++ b/ext/opentelemetry-ext-otcollector/setup.cfg
@@ -0,0 +1,49 @@
+# Copyright 2020, 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-otcollector
+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/ext/opentelemetry-ext-otcollector
+platforms = any
+license = Apache-2.0
+classifiers =
+ Development Status :: 3 - Alpha
+ 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 =
+ grpcio >= 1.0.0, < 2.0.0
+ opencensus-proto >= 0.1.0, < 1.0.0
+ opentelemetry-api >= 0.5.dev0
+ opentelemetry-sdk >= 0.5.dev0
+ protobuf >= 3.8.0
+
+[options.packages.find]
+where = src
diff --git a/ext/opentelemetry-ext-otcollector/setup.py b/ext/opentelemetry-ext-otcollector/setup.py
new file mode 100644
index 00000000000..ecd84195115
--- /dev/null
+++ b/ext/opentelemetry-ext-otcollector/setup.py
@@ -0,0 +1,26 @@
+# Copyright 2020, 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", "otcollector", "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-api/src/opentelemetry/context/propagation/__init__.py b/ext/opentelemetry-ext-otcollector/src/opentelemetry/ext/otcollector/__init__.py
similarity index 76%
rename from opentelemetry-api/src/opentelemetry/context/propagation/__init__.py
rename to ext/opentelemetry-ext-otcollector/src/opentelemetry/ext/otcollector/__init__.py
index c8706281ad7..6ab2e961ec4 100644
--- a/opentelemetry-api/src/opentelemetry/context/propagation/__init__.py
+++ b/ext/opentelemetry-ext-otcollector/src/opentelemetry/ext/otcollector/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2019, OpenTelemetry Authors
+# Copyright 2020, OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -11,8 +11,3 @@
# 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 .binaryformat import BinaryFormat
-from .httptextformat import HTTPTextFormat
-
-__all__ = ["BinaryFormat", "HTTPTextFormat"]
diff --git a/ext/opentelemetry-ext-otcollector/src/opentelemetry/ext/otcollector/trace_exporter/__init__.py b/ext/opentelemetry-ext-otcollector/src/opentelemetry/ext/otcollector/trace_exporter/__init__.py
new file mode 100644
index 00000000000..8712682ecfd
--- /dev/null
+++ b/ext/opentelemetry-ext-otcollector/src/opentelemetry/ext/otcollector/trace_exporter/__init__.py
@@ -0,0 +1,187 @@
+# Copyright 2020, 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.
+
+"""OpenTelemetry Collector Exporter."""
+
+import logging
+from typing import Optional, Sequence
+
+import grpc
+from opencensus.proto.agent.trace.v1 import (
+ trace_service_pb2,
+ trace_service_pb2_grpc,
+)
+from opencensus.proto.trace.v1 import trace_pb2
+
+import opentelemetry.ext.otcollector.util as utils
+import opentelemetry.trace as trace_api
+from opentelemetry.sdk.trace import Span, SpanContext
+from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
+from opentelemetry.trace import SpanKind, TraceState
+
+DEFAULT_ENDPOINT = "localhost:55678"
+
+logger = logging.getLogger(__name__)
+
+
+# pylint: disable=no-member
+class CollectorSpanExporter(SpanExporter):
+ """OpenTelemetry Collector span exporter.
+
+ Args:
+ endpoint: OpenTelemetry Collector OpenCensus receiver endpoint.
+ service_name: Name of Collector service.
+ host_name: Host name.
+ client: TraceService client stub.
+ """
+
+ def __init__(
+ self,
+ endpoint=DEFAULT_ENDPOINT,
+ service_name=None,
+ host_name=None,
+ client=None,
+ ):
+ self.endpoint = endpoint
+ if client is None:
+ self.channel = grpc.insecure_channel(self.endpoint)
+ self.client = trace_service_pb2_grpc.TraceServiceStub(
+ channel=self.channel
+ )
+ else:
+ self.client = client
+
+ self.node = utils.get_node(service_name, host_name)
+
+ def export(self, spans: Sequence[Span]) -> SpanExportResult:
+ try:
+ responses = self.client.Export(self.generate_span_requests(spans))
+
+ # Read response
+ for _ in responses:
+ pass
+
+ except grpc.RpcError:
+ return SpanExportResult.FAILED_NOT_RETRYABLE
+
+ return SpanExportResult.SUCCESS
+
+ def shutdown(self) -> None:
+ pass
+
+ def generate_span_requests(self, spans):
+ collector_spans = translate_to_collector(spans)
+ service_request = trace_service_pb2.ExportTraceServiceRequest(
+ node=self.node, spans=collector_spans
+ )
+ yield service_request
+
+
+# pylint: disable=too-many-branches
+def translate_to_collector(spans: Sequence[Span]):
+ collector_spans = []
+ for span in spans:
+ status = None
+ if span.status is not None:
+ status = trace_pb2.Status(
+ code=span.status.canonical_code.value,
+ message=span.status.description,
+ )
+ collector_span = trace_pb2.Span(
+ name=trace_pb2.TruncatableString(value=span.name),
+ kind=utils.get_collector_span_kind(span.kind),
+ trace_id=span.context.trace_id.to_bytes(16, "big"),
+ span_id=span.context.span_id.to_bytes(8, "big"),
+ start_time=utils.proto_timestamp_from_time_ns(span.start_time),
+ end_time=utils.proto_timestamp_from_time_ns(span.end_time),
+ status=status,
+ )
+
+ parent_id = 0
+ if isinstance(span.parent, trace_api.Span):
+ parent_id = span.parent.get_context().span_id
+ elif isinstance(span.parent, trace_api.SpanContext):
+ parent_id = span.parent.span_id
+
+ collector_span.parent_span_id = parent_id.to_bytes(8, "big")
+
+ if span.context.trace_state is not None:
+ for (key, value) in span.context.trace_state.items():
+ collector_span.tracestate.entries.add(key=key, value=value)
+
+ if span.attributes:
+ for (key, value) in span.attributes.items():
+ utils.add_proto_attribute_value(
+ collector_span.attributes, key, value
+ )
+
+ if span.events:
+ for event in span.events:
+
+ collector_annotation = trace_pb2.Span.TimeEvent.Annotation(
+ description=trace_pb2.TruncatableString(value=event.name)
+ )
+
+ if event.attributes:
+ for (key, value) in event.attributes.items():
+ utils.add_proto_attribute_value(
+ collector_annotation.attributes, key, value
+ )
+
+ collector_span.time_events.time_event.add(
+ time=utils.proto_timestamp_from_time_ns(event.timestamp),
+ annotation=collector_annotation,
+ )
+
+ if span.links:
+ for link in span.links:
+ collector_span_link = collector_span.links.link.add()
+ collector_span_link.trace_id = link.context.trace_id.to_bytes(
+ 16, "big"
+ )
+ collector_span_link.span_id = link.context.span_id.to_bytes(
+ 8, "big"
+ )
+
+ collector_span_link.type = (
+ trace_pb2.Span.Link.Type.TYPE_UNSPECIFIED
+ )
+
+ if isinstance(span.parent, trace_api.Span):
+ if (
+ link.context.span_id
+ == span.parent.get_context().span_id
+ and link.context.trace_id
+ == span.parent.get_context().trace_id
+ ):
+ collector_span_link.type = (
+ trace_pb2.Span.Link.Type.PARENT_LINKED_SPAN
+ )
+ elif isinstance(span.parent, trace_api.SpanContext):
+ if (
+ link.context.span_id == span.parent.span_id
+ and link.context.trace_id == span.parent.trace_id
+ ):
+ collector_span_link.type = (
+ trace_pb2.Span.Link.Type.PARENT_LINKED_SPAN
+ )
+
+ if link.attributes:
+ for (key, value) in link.attributes.items():
+ utils.add_proto_attribute_value(
+ collector_span_link.attributes, key, value
+ )
+
+ collector_spans.append(collector_span)
+ return collector_spans
diff --git a/ext/opentelemetry-ext-otcollector/src/opentelemetry/ext/otcollector/util.py b/ext/opentelemetry-ext-otcollector/src/opentelemetry/ext/otcollector/util.py
new file mode 100644
index 00000000000..7d605ab8f90
--- /dev/null
+++ b/ext/opentelemetry-ext-otcollector/src/opentelemetry/ext/otcollector/util.py
@@ -0,0 +1,99 @@
+# Copyright 2020, 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 socket
+import time
+
+from google.protobuf.timestamp_pb2 import Timestamp
+from opencensus.proto.agent.common.v1 import common_pb2
+from opencensus.proto.trace.v1 import trace_pb2
+
+from opentelemetry.ext.otcollector.version import (
+ __version__ as otcollector_exporter_version,
+)
+from opentelemetry.trace import SpanKind
+from opentelemetry.util.version import __version__ as opentelemetry_version
+
+
+def proto_timestamp_from_time_ns(time_ns):
+ """Converts datetime to protobuf timestamp.
+
+ Args:
+ time_ns: Time in nanoseconds
+
+ Returns:
+ Returns protobuf timestamp.
+ """
+ ts = Timestamp()
+ if time_ns is not None:
+ # pylint: disable=no-member
+ ts.FromNanoseconds(time_ns)
+ return ts
+
+
+# pylint: disable=no-member
+def get_collector_span_kind(kind: SpanKind):
+ if kind is SpanKind.SERVER:
+ return trace_pb2.Span.SpanKind.SERVER
+ if kind is SpanKind.CLIENT:
+ return trace_pb2.Span.SpanKind.CLIENT
+ return trace_pb2.Span.SpanKind.SPAN_KIND_UNSPECIFIED
+
+
+def add_proto_attribute_value(pb_attributes, key, value):
+ """Sets string, int, boolean or float value on protobuf
+ span, link or annotation attributes.
+
+ Args:
+ pb_attributes: protobuf Span's attributes property.
+ key: attribute key to set.
+ value: attribute value
+ """
+
+ if isinstance(value, bool):
+ pb_attributes.attribute_map[key].bool_value = value
+ elif isinstance(value, int):
+ pb_attributes.attribute_map[key].int_value = value
+ elif isinstance(value, str):
+ pb_attributes.attribute_map[key].string_value.value = value
+ elif isinstance(value, float):
+ pb_attributes.attribute_map[key].double_value = value
+ else:
+ pb_attributes.attribute_map[key].string_value.value = str(value)
+
+
+# pylint: disable=no-member
+def get_node(service_name, host_name):
+ """Generates Node message from params and system information.
+
+ Args:
+ service_name: Name of Collector service.
+ host_name: Host name.
+ """
+ return common_pb2.Node(
+ identifier=common_pb2.ProcessIdentifier(
+ host_name=socket.gethostname() if host_name is None else host_name,
+ pid=os.getpid(),
+ start_timestamp=proto_timestamp_from_time_ns(
+ int(time.time() * 1e9)
+ ),
+ ),
+ library_info=common_pb2.LibraryInfo(
+ language=common_pb2.LibraryInfo.Language.Value("PYTHON"),
+ exporter_version=otcollector_exporter_version,
+ core_library_version=opentelemetry_version,
+ ),
+ service_info=common_pb2.ServiceInfo(name=service_name),
+ )
diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py b/ext/opentelemetry-ext-otcollector/src/opentelemetry/ext/otcollector/version.py
similarity index 76%
rename from opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py
rename to ext/opentelemetry-ext-otcollector/src/opentelemetry/ext/otcollector/version.py
index c8706281ad7..f48cb5bee5c 100644
--- a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/__init__.py
+++ b/ext/opentelemetry-ext-otcollector/src/opentelemetry/ext/otcollector/version.py
@@ -1,4 +1,4 @@
-# Copyright 2019, OpenTelemetry Authors
+# Copyright 2020, OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,7 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from .binaryformat import BinaryFormat
-from .httptextformat import HTTPTextFormat
-
-__all__ = ["BinaryFormat", "HTTPTextFormat"]
+__version__ = "0.5.dev0"
diff --git a/ext/opentelemetry-ext-otcollector/tests/__init__.py b/ext/opentelemetry-ext-otcollector/tests/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/ext/opentelemetry-ext-otcollector/tests/test_otcollector_exporter.py b/ext/opentelemetry-ext-otcollector/tests/test_otcollector_exporter.py
new file mode 100644
index 00000000000..9a17ea6c94d
--- /dev/null
+++ b/ext/opentelemetry-ext-otcollector/tests/test_otcollector_exporter.py
@@ -0,0 +1,305 @@
+# Copyright 2020, 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
+
+import grpc
+from google.protobuf.timestamp_pb2 import Timestamp
+from opencensus.proto.trace.v1 import trace_pb2
+
+import opentelemetry.ext.otcollector.util as utils
+from opentelemetry import trace as trace_api
+from opentelemetry.ext.otcollector.trace_exporter import (
+ CollectorSpanExporter,
+ translate_to_collector,
+)
+from opentelemetry.sdk import trace
+from opentelemetry.sdk.trace.export import SpanExportResult
+from opentelemetry.trace import TraceFlags
+
+
+# pylint: disable=no-member
+class TestCollectorSpanExporter(unittest.TestCase):
+ def test_constructor(self):
+ mock_get_node = mock.Mock()
+ patch = mock.patch(
+ "opentelemetry.ext.otcollector.util.get_node",
+ side_effect=mock_get_node,
+ )
+ service_name = "testServiceName"
+ host_name = "testHostName"
+ client = grpc.insecure_channel("")
+ endpoint = "testEndpoint"
+ with patch:
+ exporter = CollectorSpanExporter(
+ service_name=service_name,
+ host_name=host_name,
+ endpoint=endpoint,
+ client=client,
+ )
+
+ self.assertIs(exporter.client, client)
+ self.assertEqual(exporter.endpoint, endpoint)
+ mock_get_node.assert_called_with(service_name, host_name)
+
+ def test_get_collector_span_kind(self):
+ result = utils.get_collector_span_kind(trace_api.SpanKind.SERVER)
+ self.assertIs(result, trace_pb2.Span.SpanKind.SERVER)
+ result = utils.get_collector_span_kind(trace_api.SpanKind.CLIENT)
+ self.assertIs(result, trace_pb2.Span.SpanKind.CLIENT)
+ result = utils.get_collector_span_kind(trace_api.SpanKind.CONSUMER)
+ self.assertIs(result, trace_pb2.Span.SpanKind.SPAN_KIND_UNSPECIFIED)
+ result = utils.get_collector_span_kind(trace_api.SpanKind.PRODUCER)
+ self.assertIs(result, trace_pb2.Span.SpanKind.SPAN_KIND_UNSPECIFIED)
+ result = utils.get_collector_span_kind(trace_api.SpanKind.INTERNAL)
+ self.assertIs(result, trace_pb2.Span.SpanKind.SPAN_KIND_UNSPECIFIED)
+
+ def test_proto_timestamp_from_time_ns(self):
+ result = utils.proto_timestamp_from_time_ns(12345)
+ self.assertIsInstance(result, Timestamp)
+ self.assertEqual(result.nanos, 12345)
+
+ # pylint: disable=too-many-locals
+ # pylint: disable=too-many-statements
+ def test_translate_to_collector(self):
+ trace_id = 0x6E0C63257DE34C926F9EFCD03927272E
+ span_id = 0x34BF92DEEFC58C92
+ parent_id = 0x1111111111111111
+ base_time = 683647322 * 10 ** 9 # in ns
+ start_times = (
+ base_time,
+ base_time + 150 * 10 ** 6,
+ base_time + 300 * 10 ** 6,
+ )
+ durations = (50 * 10 ** 6, 100 * 10 ** 6, 200 * 10 ** 6)
+ end_times = (
+ start_times[0] + durations[0],
+ start_times[1] + durations[1],
+ start_times[2] + durations[2],
+ )
+ span_context = trace_api.SpanContext(
+ trace_id,
+ span_id,
+ trace_flags=TraceFlags(TraceFlags.SAMPLED),
+ trace_state=trace_api.TraceState({"testKey": "testValue"}),
+ )
+ parent_context = trace_api.SpanContext(trace_id, parent_id)
+ other_context = trace_api.SpanContext(trace_id, span_id)
+ event_attributes = {
+ "annotation_bool": True,
+ "annotation_string": "annotation_test",
+ "key_float": 0.3,
+ }
+ event_timestamp = base_time + 50 * 10 ** 6
+ event = trace_api.Event(
+ name="event0",
+ timestamp=event_timestamp,
+ attributes=event_attributes,
+ )
+ link_attributes = {"key_bool": True}
+ link_1 = trace_api.Link(
+ context=other_context, attributes=link_attributes
+ )
+ link_2 = trace_api.Link(
+ context=parent_context, attributes=link_attributes
+ )
+ span_1 = trace.Span(
+ name="test1",
+ context=span_context,
+ parent=parent_context,
+ events=(event,),
+ links=(link_1,),
+ kind=trace_api.SpanKind.CLIENT,
+ )
+ span_2 = trace.Span(
+ name="test2",
+ context=parent_context,
+ parent=None,
+ kind=trace_api.SpanKind.SERVER,
+ )
+ span_3 = trace.Span(
+ name="test3", context=other_context, links=(link_2,), parent=span_2
+ )
+ otel_spans = [span_1, span_2, span_3]
+ otel_spans[0].start(start_time=start_times[0])
+ otel_spans[0].set_attribute("key_bool", False)
+ otel_spans[0].set_attribute("key_string", "hello_world")
+ otel_spans[0].set_attribute("key_float", 111.22)
+ otel_spans[0].set_attribute("key_int", 333)
+ otel_spans[0].set_status(
+ trace_api.Status(
+ trace_api.status.StatusCanonicalCode.INTERNAL,
+ "test description",
+ )
+ )
+ otel_spans[0].end(end_time=end_times[0])
+ otel_spans[1].start(start_time=start_times[1])
+ otel_spans[1].end(end_time=end_times[1])
+ otel_spans[2].start(start_time=start_times[2])
+ otel_spans[2].end(end_time=end_times[2])
+ output_spans = translate_to_collector(otel_spans)
+
+ self.assertEqual(len(output_spans), 3)
+ self.assertEqual(
+ output_spans[0].trace_id, b"n\x0cc%}\xe3L\x92o\x9e\xfc\xd09''."
+ )
+ self.assertEqual(
+ output_spans[0].span_id, b"4\xbf\x92\xde\xef\xc5\x8c\x92"
+ )
+ self.assertEqual(
+ output_spans[0].name, trace_pb2.TruncatableString(value="test1")
+ )
+ self.assertEqual(
+ output_spans[1].name, trace_pb2.TruncatableString(value="test2")
+ )
+ self.assertEqual(
+ output_spans[2].name, trace_pb2.TruncatableString(value="test3")
+ )
+ self.assertEqual(
+ output_spans[0].start_time.seconds,
+ int(start_times[0] / 1000000000),
+ )
+ self.assertEqual(
+ output_spans[0].end_time.seconds, int(end_times[0] / 1000000000)
+ )
+ self.assertEqual(output_spans[0].kind, trace_api.SpanKind.CLIENT.value)
+ self.assertEqual(output_spans[1].kind, trace_api.SpanKind.SERVER.value)
+
+ self.assertEqual(
+ output_spans[0].parent_span_id, b"\x11\x11\x11\x11\x11\x11\x11\x11"
+ )
+ self.assertEqual(
+ output_spans[2].parent_span_id, b"\x11\x11\x11\x11\x11\x11\x11\x11"
+ )
+ self.assertEqual(
+ output_spans[0].status.code,
+ trace_api.status.StatusCanonicalCode.INTERNAL.value,
+ )
+ self.assertEqual(output_spans[0].status.message, "test description")
+ self.assertEqual(len(output_spans[0].tracestate.entries), 1)
+ self.assertEqual(output_spans[0].tracestate.entries[0].key, "testKey")
+ self.assertEqual(
+ output_spans[0].tracestate.entries[0].value, "testValue"
+ )
+
+ self.assertEqual(
+ output_spans[0].attributes.attribute_map["key_bool"].bool_value,
+ False,
+ )
+ self.assertEqual(
+ output_spans[0]
+ .attributes.attribute_map["key_string"]
+ .string_value.value,
+ "hello_world",
+ )
+ self.assertEqual(
+ output_spans[0].attributes.attribute_map["key_float"].double_value,
+ 111.22,
+ )
+ self.assertEqual(
+ output_spans[0].attributes.attribute_map["key_int"].int_value, 333
+ )
+
+ self.assertEqual(
+ output_spans[0].time_events.time_event[0].time.seconds, 683647322
+ )
+ self.assertEqual(
+ output_spans[0]
+ .time_events.time_event[0]
+ .annotation.description.value,
+ "event0",
+ )
+ self.assertEqual(
+ output_spans[0]
+ .time_events.time_event[0]
+ .annotation.attributes.attribute_map["annotation_bool"]
+ .bool_value,
+ True,
+ )
+ self.assertEqual(
+ output_spans[0]
+ .time_events.time_event[0]
+ .annotation.attributes.attribute_map["annotation_string"]
+ .string_value.value,
+ "annotation_test",
+ )
+ self.assertEqual(
+ output_spans[0]
+ .time_events.time_event[0]
+ .annotation.attributes.attribute_map["key_float"]
+ .double_value,
+ 0.3,
+ )
+
+ self.assertEqual(
+ output_spans[0].links.link[0].trace_id,
+ b"n\x0cc%}\xe3L\x92o\x9e\xfc\xd09''.",
+ )
+ self.assertEqual(
+ output_spans[0].links.link[0].span_id,
+ b"4\xbf\x92\xde\xef\xc5\x8c\x92",
+ )
+ self.assertEqual(
+ output_spans[0].links.link[0].type,
+ trace_pb2.Span.Link.Type.TYPE_UNSPECIFIED,
+ )
+ self.assertEqual(
+ output_spans[2].links.link[0].type,
+ trace_pb2.Span.Link.Type.PARENT_LINKED_SPAN,
+ )
+ self.assertEqual(
+ output_spans[0]
+ .links.link[0]
+ .attributes.attribute_map["key_bool"]
+ .bool_value,
+ True,
+ )
+
+ def test_export(self):
+ mock_client = mock.MagicMock()
+ mock_export = mock.MagicMock()
+ mock_client.Export = mock_export
+ host_name = "testHostName"
+ collector_exporter = CollectorSpanExporter(
+ client=mock_client, host_name=host_name
+ )
+
+ trace_id = 0x6E0C63257DE34C926F9EFCD03927272E
+ span_id = 0x34BF92DEEFC58C92
+ span_context = trace_api.SpanContext(
+ trace_id, span_id, trace_flags=TraceFlags(TraceFlags.SAMPLED)
+ )
+ otel_spans = [
+ trace.Span(
+ name="test1",
+ context=span_context,
+ kind=trace_api.SpanKind.CLIENT,
+ )
+ ]
+ result_status = collector_exporter.export(otel_spans)
+ self.assertEqual(SpanExportResult.SUCCESS, result_status)
+
+ # pylint: disable=unsubscriptable-object
+ export_arg = mock_export.call_args[0]
+ service_request = next(export_arg[0])
+ output_spans = getattr(service_request, "spans")
+ output_node = getattr(service_request, "node")
+ self.assertEqual(len(output_spans), 1)
+ self.assertIsNotNone(getattr(output_node, "library_info"))
+ self.assertIsNotNone(getattr(output_node, "service_info"))
+ output_identifier = getattr(output_node, "identifier")
+ self.assertEqual(
+ getattr(output_identifier, "host_name"), "testHostName"
+ )
diff --git a/ext/opentelemetry-ext-prometheus/README.rst b/ext/opentelemetry-ext-prometheus/README.rst
index 2d968d6a7c8..e70332556e2 100644
--- a/ext/opentelemetry-ext-prometheus/README.rst
+++ b/ext/opentelemetry-ext-prometheus/README.rst
@@ -1,5 +1,5 @@
OpenTelemetry Prometheus Exporter
-=============================
+=================================
|pypi|
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 5b4a17a5569..ebe68e3f4dd 100644
--- a/ext/opentelemetry-ext-prometheus/src/opentelemetry/ext/prometheus/__init__.py
+++ b/ext/opentelemetry-ext-prometheus/src/opentelemetry/ext/prometheus/__init__.py
@@ -24,11 +24,10 @@
REGISTRY,
CollectorRegistry,
CounterMetricFamily,
- GaugeMetricFamily,
UnknownMetricFamily,
)
-from opentelemetry.metrics import Counter, Gauge, Measure, Metric
+from opentelemetry.metrics import Counter, Measure, Metric
from opentelemetry.sdk.metrics.export import (
MetricRecord,
MetricsExporter,
@@ -112,17 +111,6 @@ def _translate_to_prometheus(self, metric_record: MetricRecord):
prometheus_metric.add_metric(
labels=label_values, value=metric_record.aggregator.checkpoint
)
-
- elif isinstance(metric_record.metric, Gauge):
- prometheus_metric = GaugeMetricFamily(
- name=metric_name,
- documentation=metric_record.metric.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, Measure):
prometheus_metric = UnknownMetricFamily(
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 6b39cd19b59..f48cb5bee5c 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.4.dev0"
+__version__ = "0.5.dev0"
diff --git a/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py b/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py
index 94fea96c5b5..f6883475386 100644
--- a/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py
+++ b/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py
@@ -28,7 +28,7 @@
class TestPrometheusMetricExporter(unittest.TestCase):
def setUp(self):
- self._meter = metrics.Meter()
+ self._meter = metrics.MeterProvider().get_meter(__name__)
self._test_metric = self._meter.create_metric(
"testname",
"testdesc",
@@ -74,7 +74,7 @@ def test_export(self):
self.assertIs(result, MetricsExportResult.SUCCESS)
def test_counter_to_prometheus(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
metric = meter.create_metric(
"test@name",
"testdesc",
@@ -111,7 +111,7 @@ def test_counter_to_prometheus(self):
def test_invalid_metric(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
metric = meter.create_metric(
"tesname", "testdesc", "unit", int, TestMetric
)
diff --git a/ext/opentelemetry-ext-psycopg2/README.rst b/ext/opentelemetry-ext-psycopg2/README.rst
index 9399c80facc..127b74f0c79 100644
--- a/ext/opentelemetry-ext-psycopg2/README.rst
+++ b/ext/opentelemetry-ext-psycopg2/README.rst
@@ -4,18 +4,19 @@ OpenTelemetry Psycopg integration
The integration with PostgreSQL supports the `Psycopg`_ library and is specified
to ``trace_integration`` using ``'PostgreSQL'``.
-.. Psycopg: http://initd.org/psycopg/
+.. _Psycopg: http://initd.org/psycopg/
Usage
-----
-.. code:: python
+.. code-block:: python
+
import psycopg2
from opentelemetry import trace
- from opentelemetry.sdk.trace import TracerSource
+ from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.trace.ext.psycopg2 import trace_integration
- trace.set_preferred_tracer_source_implementation(lambda T: TracerSource())
+ trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
tracer = trace.get_tracer(__name__)
trace_integration(tracer)
cnx = psycopg2.connect(database='Database')
@@ -26,4 +27,4 @@ Usage
References
----------
-* `OpenTelemetry Project `_
\ No newline at end of file
+* `OpenTelemetry Project `_
diff --git a/ext/opentelemetry-ext-psycopg2/setup.cfg b/ext/opentelemetry-ext-psycopg2/setup.cfg
index f26c5918ebf..a4a67e20197 100644
--- a/ext/opentelemetry-ext-psycopg2/setup.cfg
+++ b/ext/opentelemetry-ext-psycopg2/setup.cfg
@@ -39,7 +39,7 @@ package_dir=
=src
packages=find_namespace:
install_requires =
- opentelemetry-api >= 0.4.dev0
+ opentelemetry-api >= 0.5.dev0
psycopg2-binary >= 2.7.3.1
wrapt >= 1.0.0, < 2.0.0
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 6b39cd19b59..f48cb5bee5c 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.4.dev0"
+__version__ = "0.5.dev0"
diff --git a/ext/opentelemetry-ext-pymongo/README.rst b/ext/opentelemetry-ext-pymongo/README.rst
index e8a42084be5..a4ecd9c904f 100644
--- a/ext/opentelemetry-ext-pymongo/README.rst
+++ b/ext/opentelemetry-ext-pymongo/README.rst
@@ -12,10 +12,10 @@ Usage
.. code:: python
from pymongo import MongoClient
- from opentelemetry.trace import tracer_source
+ from opentelemetry.trace import tracer_provider
from opentelemetry.trace.ext.pymongo import trace_integration
- trace_integration(tracer_source())
+ trace_integration(tracer_provider())
client = MongoClient()
db = client["MongoDB_Database"]
collection = db["MongoDB_Collection"]
diff --git a/ext/opentelemetry-ext-pymongo/setup.cfg b/ext/opentelemetry-ext-pymongo/setup.cfg
index ecc0ce3b778..0f6a06ea7fd 100644
--- a/ext/opentelemetry-ext-pymongo/setup.cfg
+++ b/ext/opentelemetry-ext-pymongo/setup.cfg
@@ -39,7 +39,7 @@ package_dir=
=src
packages=find_namespace:
install_requires =
- opentelemetry-api >= 0.4.dev0
+ opentelemetry-api >= 0.5.dev0
pymongo ~= 3.1
[options.packages.find]
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 2f792fff802..d13bf967481 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.4.dev0"
+__version__ = "0.5.dev0"
diff --git a/ext/opentelemetry-ext-testutil/src/opentelemetry/ext/testutil/version.py b/ext/opentelemetry-ext-testutil/src/opentelemetry/ext/testutil/version.py
index 116884f3557..9aea0d23ea1 100644
--- a/ext/opentelemetry-ext-testutil/src/opentelemetry/ext/testutil/version.py
+++ b/ext/opentelemetry-ext-testutil/src/opentelemetry/ext/testutil/version.py
@@ -1 +1 @@
-__version__ = "0.4.dev0"
+__version__ = "0.5.dev0"
diff --git a/ext/opentelemetry-ext-testutil/src/opentelemetry/ext/testutil/wsgitestutil.py b/ext/opentelemetry-ext-testutil/src/opentelemetry/ext/testutil/wsgitestutil.py
index 5f99d08df04..18b64364db9 100644
--- a/ext/opentelemetry-ext-testutil/src/opentelemetry/ext/testutil/wsgitestutil.py
+++ b/ext/opentelemetry-ext-testutil/src/opentelemetry/ext/testutil/wsgitestutil.py
@@ -4,7 +4,7 @@
from importlib import reload
from opentelemetry import trace as trace_api
-from opentelemetry.sdk.trace import TracerSource, export
+from opentelemetry.sdk.trace import TracerProvider, export
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
InMemorySpanExporter,
)
@@ -16,13 +16,13 @@ class WsgiTestBase(unittest.TestCase):
@classmethod
def setUpClass(cls):
global _MEMORY_EXPORTER # pylint:disable=global-statement
- trace_api.set_preferred_tracer_source_implementation(
- lambda T: TracerSource()
+ trace_api.set_preferred_tracer_provider_implementation(
+ lambda T: TracerProvider()
)
- tracer_source = trace_api.tracer_source()
+ tracer_provider = trace_api.tracer_provider()
_MEMORY_EXPORTER = InMemorySpanExporter()
span_processor = export.SimpleExportSpanProcessor(_MEMORY_EXPORTER)
- tracer_source.add_span_processor(span_processor)
+ tracer_provider.add_span_processor(span_processor)
@classmethod
def tearDownClass(cls):
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 37a3a0e9e0d..b96fc057d14 100644
--- a/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py
+++ b/ext/opentelemetry-ext-wsgi/src/opentelemetry/ext/wsgi/__init__.py
@@ -22,8 +22,9 @@
import typing
import wsgiref.util as wsgiref_util
-from opentelemetry import propagators, trace
+from opentelemetry import context, propagators, trace
from opentelemetry.ext.wsgi.version import __version__
+from opentelemetry.trace.propagation import get_span_from_context
from opentelemetry.trace.status import Status, StatusCanonicalCode
_HTTP_VERSION_PREFIX = "HTTP/"
@@ -181,12 +182,13 @@ def __call__(self, environ, start_response):
start_response: The WSGI start_response callable.
"""
- parent_span = propagators.extract(get_header_from_environ, environ)
+ token = context.attach(
+ propagators.extract(get_header_from_environ, environ)
+ )
span_name = get_default_span_name(environ)
span = self.tracer.start_span(
span_name,
- parent_span,
kind=trace.SpanKind.SERVER,
attributes=collect_request_attributes(environ),
)
@@ -197,17 +199,20 @@ def __call__(self, environ, start_response):
span, start_response
)
iterable = self.wsgi(environ, start_response)
- return _end_span_after_iterating(iterable, span, self.tracer)
+ return _end_span_after_iterating(
+ iterable, span, self.tracer, token
+ )
except: # noqa
# TODO Set span status (cf. https://github.com/open-telemetry/opentelemetry-python/issues/292)
span.end()
+ context.detach(token)
raise
# Put this in a subfunction to not delay the call to the wrapped
# WSGI application (instrumentation should change the application
# behavior as little as possible).
-def _end_span_after_iterating(iterable, span, tracer):
+def _end_span_after_iterating(iterable, span, tracer, token):
try:
with tracer.use_span(span):
for yielded in iterable:
@@ -217,3 +222,4 @@ def _end_span_after_iterating(iterable, span, tracer):
if close:
close()
span.end()
+ context.detach(token)
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 2f792fff802..d13bf967481 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.4.dev0"
+__version__ = "0.5.dev0"
diff --git a/ext/opentelemetry-ext-zipkin/README.rst b/ext/opentelemetry-ext-zipkin/README.rst
index 57dd4b7faa1..f933ba4a685 100644
--- a/ext/opentelemetry-ext-zipkin/README.rst
+++ b/ext/opentelemetry-ext-zipkin/README.rst
@@ -30,10 +30,10 @@ This exporter always send traces to the configured Zipkin collector using HTTP.
from opentelemetry import trace
from opentelemetry.ext import zipkin
- from opentelemetry.sdk.trace import TracerSource
+ from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchExportSpanProcessor
- trace.set_preferred_tracer_source_implementation(lambda T: TracerSource())
+ trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
tracer = trace.get_tracer(__name__)
# create a ZipkinSpanExporter
@@ -53,7 +53,7 @@ This exporter always send traces to the configured Zipkin collector using HTTP.
span_processor = BatchExportSpanProcessor(zipkin_exporter)
# add to the tracer
- trace.tracer_source().add_span_processor(span_processor)
+ trace.tracer_provider().add_span_processor(span_processor)
with tracer.start_as_current_span("foo"):
print("Hello world!")
diff --git a/ext/opentelemetry-ext-zipkin/src/opentelemetry/ext/zipkin/__init__.py b/ext/opentelemetry-ext-zipkin/src/opentelemetry/ext/zipkin/__init__.py
index fec4da8c3ed..077fa9a6b48 100644
--- a/ext/opentelemetry-ext-zipkin/src/opentelemetry/ext/zipkin/__init__.py
+++ b/ext/opentelemetry-ext-zipkin/src/opentelemetry/ext/zipkin/__init__.py
@@ -132,7 +132,7 @@ def _translate_to_zipkin(self, spans: Sequence[Span]):
"annotations": _extract_annotations_from_events(span.events),
}
- if context.trace_options.sampled:
+ if context.trace_flags.sampled:
zipkin_span["debug"] = 1
if isinstance(span.parent, Span):
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 93ef792d051..d13bf967481 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.3.dev0"
+__version__ = "0.5.dev0"
diff --git a/ext/opentelemetry-ext-zipkin/tests/test_zipkin_exporter.py b/ext/opentelemetry-ext-zipkin/tests/test_zipkin_exporter.py
index 467bc610bd8..c779c7388f6 100644
--- a/ext/opentelemetry-ext-zipkin/tests/test_zipkin_exporter.py
+++ b/ext/opentelemetry-ext-zipkin/tests/test_zipkin_exporter.py
@@ -20,7 +20,7 @@
from opentelemetry.ext.zipkin import ZipkinSpanExporter
from opentelemetry.sdk import trace
from opentelemetry.sdk.trace.export import SpanExportResult
-from opentelemetry.trace import TraceOptions
+from opentelemetry.trace import TraceFlags
class MockResponse:
@@ -114,7 +114,7 @@ def test_export(self):
)
span_context = trace_api.SpanContext(
- trace_id, span_id, trace_options=TraceOptions(TraceOptions.SAMPLED)
+ trace_id, span_id, trace_flags=TraceFlags(TraceFlags.SAMPLED)
)
parent_context = trace_api.SpanContext(trace_id, parent_id)
other_context = trace_api.SpanContext(trace_id, other_id)
diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py
index 1d1b53e7cb2..1ac837f51c5 100644
--- a/opentelemetry-api/src/opentelemetry/context/__init__.py
+++ b/opentelemetry-api/src/opentelemetry/context/__init__.py
@@ -14,6 +14,7 @@
import logging
import typing
+from functools import wraps
from os import environ
from sys import version_info
@@ -25,6 +26,47 @@
_RUNTIME_CONTEXT = None # type: typing.Optional[RuntimeContext]
+_F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any])
+
+
+def _load_runtime_context(func: _F) -> _F:
+ """A decorator used to initialize the global RuntimeContext
+
+ Returns:
+ A wrapper of the decorated method.
+ """
+
+ @wraps(func) # type: ignore
+ def wrapper(
+ *args: typing.Tuple[typing.Any, typing.Any],
+ **kwargs: typing.Dict[typing.Any, typing.Any]
+ ) -> typing.Optional[typing.Any]:
+ global _RUNTIME_CONTEXT # pylint: disable=global-statement
+ if _RUNTIME_CONTEXT is None:
+ # FIXME use a better implementation of a configuration manager to avoid having
+ # to get configuration values straight from environment variables
+ if version_info < (3, 5):
+ # contextvars are not supported in 3.4, use thread-local storage
+ default_context = "threadlocal_context"
+ else:
+ default_context = "contextvars_context"
+
+ configured_context = environ.get(
+ "OPENTELEMETRY_CONTEXT", default_context
+ ) # type: str
+ try:
+ _RUNTIME_CONTEXT = next(
+ iter_entry_points(
+ "opentelemetry_context", configured_context
+ )
+ ).load()()
+ except Exception: # pylint: disable=broad-except
+ logger.error("Failed to load context: %s", configured_context)
+ return func(*args, **kwargs) # type: ignore
+
+ return wrapper # type:ignore
+
+
def get_value(key: str, context: typing.Optional[Context] = None) -> "object":
"""To access the local state of a concern, the RuntimeContext API
provides a function which takes a context and a key as input,
@@ -33,6 +75,9 @@ def get_value(key: str, context: typing.Optional[Context] = None) -> "object":
Args:
key: The key of the value to retrieve.
context: The context from which to retrieve the value, if None, the current context is used.
+
+ Returns:
+ The value associated with the key.
"""
return context.get(key) if context is not None else get_current().get(key)
@@ -46,91 +91,55 @@ def set_value(
which contains the new value.
Args:
- key: The key of the entry to set
- value: The value of the entry to set
- context: The context to copy, if None, the current context is used
- """
- if context is None:
- context = get_current()
- new_values = context.copy()
- new_values[key] = value
- return Context(new_values)
+ key: The key of the entry to set.
+ value: The value of the entry to set.
+ context: The context to copy, if None, the current context is used.
-
-def remove_value(
- key: str, context: typing.Optional[Context] = None
-) -> Context:
- """To remove a value, this method returns a new context with the key
- cleared. Note that the removed value still remains present in the old
- context.
-
- Args:
- key: The key of the entry to remove
- context: The context to copy, if None, the current context is used
+ Returns:
+ A new `Context` containing the value set.
"""
if context is None:
context = get_current()
new_values = context.copy()
- new_values.pop(key, None)
+ new_values[key] = value
return Context(new_values)
+@_load_runtime_context # type: ignore
def get_current() -> Context:
"""To access the context associated with program execution,
- the RuntimeContext API provides a function which takes no arguments
- and returns a RuntimeContext.
- """
-
- global _RUNTIME_CONTEXT # pylint: disable=global-statement
- if _RUNTIME_CONTEXT is None:
- # FIXME use a better implementation of a configuration manager to avoid having
- # to get configuration values straight from environment variables
- if version_info < (3, 5):
- # contextvars are not supported in 3.4, use thread-local storage
- default_context = "threadlocal_context"
- else:
- default_context = "contextvars_context"
-
- configured_context = environ.get(
- "OPENTELEMETRY_CONTEXT", default_context
- ) # type: str
- try:
- _RUNTIME_CONTEXT = next(
- iter_entry_points("opentelemetry_context", configured_context)
- ).load()()
- except Exception: # pylint: disable=broad-except
- logger.error("Failed to load context: %s", configured_context)
+ the Context API provides a function which takes no arguments
+ and returns a Context.
+ Returns:
+ The current `Context` object.
+ """
return _RUNTIME_CONTEXT.get_current() # type:ignore
-def set_current(context: Context) -> Context:
- """To associate a context with program execution, the Context
- API provides a function which takes a Context.
+@_load_runtime_context # type: ignore
+def attach(context: Context) -> object:
+ """Associates a Context with the caller's current execution unit. Returns
+ a token that can be used to restore the previous Context.
Args:
- context: The context to use as current.
- """
- old_context = get_current()
- _RUNTIME_CONTEXT.set_current(context) # type:ignore
- return old_context
-
+ context: The Context to set as current.
-def with_current_context(
- func: typing.Callable[..., "object"]
-) -> typing.Callable[..., "object"]:
- """Capture the current context and apply it to the provided func."""
+ Returns:
+ A token that can be used with `detach` to reset the context.
+ """
+ return _RUNTIME_CONTEXT.attach(context) # type:ignore
- caller_context = get_current()
- def call_with_current_context(
- *args: "object", **kwargs: "object"
- ) -> "object":
- try:
- backup = get_current()
- set_current(caller_context)
- return func(*args, **kwargs)
- finally:
- set_current(backup)
+@_load_runtime_context # type: ignore
+def detach(token: object) -> None:
+ """Resets the Context associated with the caller's current execution unit
+ to the value it had before attaching a specified Context.
- return call_with_current_context
+ Args:
+ token: The Token that was returned by a previous call to attach a Context.
+ """
+ try:
+ _RUNTIME_CONTEXT.detach(token) # type: ignore
+ except Exception: # pylint: disable=broad-except
+ logger.error("Failed to detach context")
diff --git a/opentelemetry-api/src/opentelemetry/context/context.py b/opentelemetry-api/src/opentelemetry/context/context.py
index 148312a884c..1c7cfba9634 100644
--- a/opentelemetry-api/src/opentelemetry/context/context.py
+++ b/opentelemetry-api/src/opentelemetry/context/context.py
@@ -29,8 +29,9 @@ class RuntimeContext(ABC):
"""
@abstractmethod
- def set_current(self, context: Context) -> None:
- """ Sets the current `Context` object.
+ def attach(self, context: Context) -> object:
+ """ Sets the current `Context` object. Returns a
+ token that can be used to reset to the previous `Context`.
Args:
context: The Context to set.
@@ -40,5 +41,13 @@ def set_current(self, context: Context) -> None:
def get_current(self) -> Context:
""" Returns the current `Context` object. """
+ @abstractmethod
+ def detach(self, token: object) -> None:
+ """ Resets Context to a previous value
+
+ Args:
+ token: A reference to a previous Context.
+ """
+
__all__ = ["Context", "RuntimeContext"]
diff --git a/opentelemetry-api/src/opentelemetry/context/contextvars_context.py b/opentelemetry-api/src/opentelemetry/context/contextvars_context.py
index 1fd202275a3..0d075e0776a 100644
--- a/opentelemetry-api/src/opentelemetry/context/contextvars_context.py
+++ b/opentelemetry-api/src/opentelemetry/context/contextvars_context.py
@@ -35,13 +35,17 @@ def __init__(self) -> None:
self._CONTEXT_KEY, default=Context()
)
- def set_current(self, context: Context) -> None:
- """See `opentelemetry.context.RuntimeContext.set_current`."""
- self._current_context.set(context)
+ def attach(self, context: Context) -> object:
+ """See `opentelemetry.context.RuntimeContext.attach`."""
+ return self._current_context.set(context)
def get_current(self) -> Context:
"""See `opentelemetry.context.RuntimeContext.get_current`."""
return self._current_context.get()
+ def detach(self, token: object) -> None:
+ """See `opentelemetry.context.RuntimeContext.detach`."""
+ self._current_context.reset(token) # type: ignore
+
__all__ = ["ContextVarsRuntimeContext"]
diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/binaryformat.py b/opentelemetry-api/src/opentelemetry/context/propagation/binaryformat.py
deleted file mode 100644
index 7f1a65882f3..00000000000
--- a/opentelemetry-api/src/opentelemetry/context/propagation/binaryformat.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright 2019, 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 typing
-
-from opentelemetry.trace import SpanContext
-
-
-class BinaryFormat(abc.ABC):
- """API for serialization of span context into binary formats.
-
- This class provides an interface that enables converting span contexts
- to and from a binary format.
- """
-
- @staticmethod
- @abc.abstractmethod
- def to_bytes(context: SpanContext) -> bytes:
- """Creates a byte representation of a SpanContext.
-
- to_bytes should read values from a SpanContext and return a data
- format to represent it, in bytes.
-
- Args:
- context: the SpanContext to serialize
-
- Returns:
- A bytes representation of the SpanContext.
-
- """
-
- @staticmethod
- @abc.abstractmethod
- def from_bytes(byte_representation: bytes) -> typing.Optional[SpanContext]:
- """Return a SpanContext that was represented by bytes.
-
- from_bytes should return back a SpanContext that was constructed from
- the data serialized in the byte_representation passed. If it is not
- possible to read in a proper SpanContext, return None.
-
- Args:
- byte_representation: the bytes to deserialize
-
- Returns:
- A bytes representation of the SpanContext if it is valid.
- Otherwise return None.
-
- """
diff --git a/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py b/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py
index 899ab863262..6a0e76bb693 100644
--- a/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py
+++ b/opentelemetry-api/src/opentelemetry/context/threadlocal_context.py
@@ -23,14 +23,20 @@ class ThreadLocalRuntimeContext(RuntimeContext):
implementation is available for usage with Python 3.4.
"""
+ class Token:
+ def __init__(self, context: Context) -> None:
+ self._context = context
+
_CONTEXT_KEY = "current_context"
def __init__(self) -> None:
self._current_context = threading.local()
- def set_current(self, context: Context) -> None:
- """See `opentelemetry.context.RuntimeContext.set_current`."""
+ def attach(self, context: Context) -> object:
+ """See `opentelemetry.context.RuntimeContext.attach`."""
+ current = self.get_current()
setattr(self._current_context, self._CONTEXT_KEY, context)
+ return self.Token(current)
def get_current(self) -> Context:
"""See `opentelemetry.context.RuntimeContext.get_current`."""
@@ -43,5 +49,12 @@ def get_current(self) -> Context:
) # type: Context
return context
+ def detach(self, token: object) -> None:
+ """See `opentelemetry.context.RuntimeContext.detach`."""
+ if not isinstance(token, self.Token):
+ raise ValueError("invalid token")
+ # pylint: disable=protected-access
+ setattr(self._current_context, self._CONTEXT_KEY, token._context)
+
__all__ = ["ThreadLocalRuntimeContext"]
diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py
index a89d9825502..dbc7b7e79bd 100644
--- a/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py
+++ b/opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py
@@ -17,7 +17,7 @@
import typing
from contextlib import contextmanager
-from opentelemetry.context import get_value, set_current, set_value
+from opentelemetry.context import attach, get_value, set_value
from opentelemetry.context.context import Context
PRINTABLE = frozenset(
@@ -142,4 +142,4 @@ def distributed_context_from_context(
def with_distributed_context(
dctx: DistributedContext, context: typing.Optional[Context] = None
) -> None:
- set_current(set_value(_DISTRIBUTED_CONTEXT_KEY, dctx, context=context))
+ attach(set_value(_DISTRIBUTED_CONTEXT_KEY, dctx, context=context))
diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py
deleted file mode 100644
index d6d083c0dae..00000000000
--- a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/binaryformat.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# Copyright 2019, 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 typing
-
-from opentelemetry.distributedcontext import DistributedContext
-
-
-class BinaryFormat(abc.ABC):
- """API for serialization of span context into binary formats.
-
- This class provides an interface that enables converting span contexts
- to and from a binary format.
- """
-
- @staticmethod
- @abc.abstractmethod
- def to_bytes(context: DistributedContext) -> bytes:
- """Creates a byte representation of a DistributedContext.
-
- to_bytes should read values from a DistributedContext and return a data
- format to represent it, in bytes.
-
- Args:
- context: the DistributedContext to serialize
-
- Returns:
- A bytes representation of the DistributedContext.
-
- """
-
- @staticmethod
- @abc.abstractmethod
- def from_bytes(
- byte_representation: bytes,
- ) -> typing.Optional[DistributedContext]:
- """Return a DistributedContext that was represented by bytes.
-
- from_bytes should return back a DistributedContext that was constructed
- from the data serialized in the byte_representation passed. If it is
- not possible to read in a proper DistributedContext, return None.
-
- Args:
- byte_representation: the bytes to deserialize
-
- Returns:
- A bytes representation of the DistributedContext if it is valid.
- Otherwise return None.
-
- """
diff --git a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py
deleted file mode 100644
index 3e2c186283c..00000000000
--- a/opentelemetry-api/src/opentelemetry/distributedcontext/propagation/httptextformat.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# Copyright 2019, 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 typing
-
-from opentelemetry.distributedcontext import DistributedContext
-
-Setter = typing.Callable[[object, str, str], None]
-Getter = typing.Callable[[object, str], typing.List[str]]
-
-
-class HTTPTextFormat(abc.ABC):
- """API for propagation of span context via headers.
-
- This class provides an interface that enables extracting and injecting
- span context into headers of HTTP requests. HTTP frameworks and clients
- can integrate with HTTPTextFormat by providing the object containing the
- headers, and a getter and setter function for the extraction and
- injection of values, respectively.
-
- Example::
-
- import flask
- import requests
- from opentelemetry.context.propagation import HTTPTextFormat
-
- PROPAGATOR = HTTPTextFormat()
-
- def get_header_from_flask_request(request, key):
- return request.headers.get_all(key)
-
- def set_header_into_requests_request(request: requests.Request,
- key: str, value: str):
- request.headers[key] = value
-
- def example_route():
- distributed_context = PROPAGATOR.extract(
- get_header_from_flask_request,
- flask.request
- )
- request_to_downstream = requests.Request(
- "GET", "http://httpbin.org/get"
- )
- PROPAGATOR.inject(
- distributed_context,
- set_header_into_requests_request,
- request_to_downstream
- )
- session = requests.Session()
- session.send(request_to_downstream.prepare())
-
-
- .. _Propagation API Specification:
- https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md
- """
-
- @abc.abstractmethod
- def extract(
- self, get_from_carrier: Getter, carrier: object
- ) -> DistributedContext:
- """Create a DistributedContext from values in the carrier.
-
- The extract function should retrieve values from the carrier
- object using get_from_carrier, and use values to populate a
- DistributedContext value and return it.
-
- Args:
- get_from_carrier: a function that can retrieve zero
- or more values from the carrier. In the case that
- the value does not exist, return an empty list.
- carrier: and object which contains values that are
- used to construct a DistributedContext. This object
- must be paired with an appropriate get_from_carrier
- which understands how to extract a value from it.
- Returns:
- A DistributedContext with configuration found in the carrier.
-
- """
-
- @abc.abstractmethod
- def inject(
- self,
- context: DistributedContext,
- set_in_carrier: Setter,
- carrier: object,
- ) -> None:
- """Inject values from a DistributedContext into a carrier.
-
- inject enables the propagation of values into HTTP clients or
- other objects which perform an HTTP request. Implementations
- should use the set_in_carrier method to set values on the
- carrier.
-
- Args:
- context: The DistributedContext to read values from.
- set_in_carrier: A setter function that can set values
- on the carrier.
- carrier: An object that a place to define HTTP headers.
- Should be paired with set_in_carrier, which should
- know how to set header values on the carrier.
-
- """
diff --git a/opentelemetry-api/src/opentelemetry/metrics/__init__.py b/opentelemetry-api/src/opentelemetry/metrics/__init__.py
index 5045c38eed9..3ba9bcad009 100644
--- a/opentelemetry-api/src/opentelemetry/metrics/__init__.py
+++ b/opentelemetry-api/src/opentelemetry/metrics/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2019, OpenTelemetry Authors
+# Copyright 2020, OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -27,10 +27,13 @@
"""
import abc
+import logging
from typing import Callable, Dict, Optional, Sequence, Tuple, Type, TypeVar
from opentelemetry.util import loader
+logger = logging.getLogger(__name__)
+
ValueT = TypeVar("ValueT", int, float)
@@ -47,13 +50,6 @@ def add(self, value: ValueT) -> None:
value: The value to add to the handle.
"""
- def set(self, value: ValueT) -> None:
- """No-op implementation of `GaugeHandle` set.
-
- Args:
- value: The value to set to the handle.
- """
-
def record(self, value: ValueT) -> None:
"""No-op implementation of `MeasureHandle` record.
@@ -71,15 +67,6 @@ def add(self, value: ValueT) -> None:
"""
-class GaugeHandle:
- def set(self, value: ValueT) -> None:
- """Sets the current value of the handle to ``value``.
-
- Args:
- value: The value to set to the handle.
- """
-
-
class MeasureHandle:
def record(self, value: ValueT) -> None:
"""Records the given ``value`` to this handle.
@@ -121,7 +108,7 @@ def get_handle(self, label_set: LabelSet) -> "object":
Handles are useful to reduce the cost of repeatedly recording a metric
with a pre-defined set of label values. All metric kinds (counter,
- gauge, measure) support declaring a set of required label keys. The
+ measure) support declaring a set of required label keys. The
values corresponding to these keys should be specified in every handle.
"Unspecified" label values, in cases where a handle is requested but
a value was not provided are permitted.
@@ -150,14 +137,6 @@ def add(self, value: ValueT, label_set: LabelSet) -> None:
label_set: `LabelSet` to associate with the returned handle.
"""
- def set(self, value: ValueT, label_set: LabelSet) -> None:
- """No-op implementation of `Gauge` set.
-
- Args:
- value: The value to set the gauge metric to.
- label_set: `LabelSet` to associate with the returned handle.
- """
-
def record(self, value: ValueT, label_set: LabelSet) -> None:
"""No-op implementation of `Measure` record.
@@ -183,28 +162,6 @@ def add(self, value: ValueT, label_set: LabelSet) -> None:
"""
-class Gauge(Metric):
- """A gauge type metric that expresses a pre-calculated value.
-
- Gauge metrics have a value that is either ``Set`` by explicit
- instrumentation or observed through a callback. This kind of metric
- should be used when the metric cannot be expressed as a sum or because
- the measurement interval is arbitrary.
- """
-
- def get_handle(self, label_set: LabelSet) -> "GaugeHandle":
- """Gets a `GaugeHandle`."""
- return GaugeHandle()
-
- def set(self, value: ValueT, label_set: LabelSet) -> None:
- """Sets the value of the gauge to ``value``.
-
- Args:
- value: The value to set the gauge metric to.
- label_set: `LabelSet` to associate with the returned handle.
- """
-
-
class Measure(Metric):
"""A measure type metric that represent raw stats that are recorded.
@@ -224,15 +181,97 @@ def record(self, value: ValueT, label_set: LabelSet) -> None:
"""
-MetricT = TypeVar("MetricT", Counter, Gauge, Measure)
+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.
+ """
+
+ @abc.abstractmethod
+ def observe(self, value: ValueT, label_set: LabelSet) -> None:
+ """Captures ``value`` to the observer.
+
+ Args:
+ value: The value to capture to this observer metric.
+ label_set: `LabelSet` associated to ``value``.
+ """
+
+
+class DefaultObserver(Observer):
+ """No-op implementation of ``Observer``."""
+
+ def observe(self, value: ValueT, label_set: LabelSet) -> None:
+ """Captures ``value`` to the observer.
+
+ Args:
+ value: The value to capture to this observer metric.
+ label_set: `LabelSet` associated to ``value``.
+ """
+
+
+class MeterProvider(abc.ABC):
+ @abc.abstractmethod
+ def get_meter(
+ self,
+ instrumenting_module_name: str,
+ stateful: bool = True,
+ instrumenting_library_version: str = "",
+ ) -> "Meter":
+ """Returns a `Meter` for use by the given instrumentation library.
+
+ This function may return different `Meter` types (e.g. a no-op meter
+ vs. a functional meter).
+
+ Args:
+ instrumenting_module_name: The name of the instrumenting module
+ (usually just ``__name__``).
+
+ This should *not* be the name of the module that is
+ instrumented but the name of the module doing the instrumentation.
+ E.g., instead of ``"requests"``, use
+ ``"opentelemetry.ext.http_requests"``.
+
+ stateful: True/False to indicate whether the meter will be
+ stateful. True indicates the meter computes checkpoints
+ from over the process lifetime. False indicates the meter
+ computes checkpoints which describe the updates of a single
+ collection period (deltas).
+
+ instrumenting_library_version: Optional. The version string of the
+ instrumenting library. Usually this should be the same as
+ ``pkg_resources.get_distribution(instrumenting_library_name).version``.
+ """
+
+
+class DefaultMeterProvider(MeterProvider):
+ """The default MeterProvider, used when no implementation is available.
+
+ All operations are no-op.
+ """
+
+ def get_meter(
+ self,
+ instrumenting_module_name: str,
+ stateful: bool = True,
+ instrumenting_library_version: str = "",
+ ) -> "Meter":
+ # pylint:disable=no-self-use,unused-argument
+ return DefaultMeter()
+
+
+MetricT = TypeVar("MetricT", Counter, Measure, Observer)
+ObserverCallbackT = Callable[[Observer], None]
# pylint: disable=unused-argument
class Meter(abc.ABC):
"""An interface to allow the recording of metrics.
- `Metric` s are used for recording pre-defined aggregation (gauge and
- counter), or raw values (measure) in which the aggregation and labels
+ `Metric` s are used for recording pre-defined aggregation (counter),
+ or raw values (measure) in which the aggregation and labels
for the exported metric are deferred.
"""
@@ -272,7 +311,8 @@ def create_metric(
Args:
name: The name of the metric.
description: Human-readable description of the metric.
- unit: Unit of the metric values.
+ 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.
metric_type: The type of metric being created.
label_keys: The keys for the labels with dynamic values.
@@ -280,6 +320,32 @@ def create_metric(
Returns: A new ``metric_type`` metric with values of ``value_type``.
"""
+ @abc.abstractmethod
+ def register_observer(
+ self,
+ callback: ObserverCallbackT,
+ name: str,
+ description: str,
+ unit: str,
+ value_type: Type[ValueT],
+ label_keys: Sequence[str] = (),
+ enabled: bool = True,
+ ) -> "Observer":
+ """Registers an ``Observer`` metric instrument.
+
+ Args:
+ callback: Callback invoked each collection interval with the
+ observer as argument.
+ name: The name of the metric.
+ description: Human-readable description of the metric.
+ 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.
+ label_keys: The keys for the labels with dynamic values.
+ enabled: Whether to report the metric by default.
+ Returns: A new ``Observer`` metric instrument.
+ """
+
@abc.abstractmethod
def get_label_set(self, labels: Dict[str, str]) -> "LabelSet":
"""Gets a `LabelSet` with the given labels.
@@ -314,6 +380,18 @@ def create_metric(
# pylint: disable=no-self-use
return DefaultMetric()
+ def register_observer(
+ self,
+ callback: ObserverCallbackT,
+ name: str,
+ description: str,
+ unit: str,
+ value_type: Type[ValueT],
+ label_keys: Sequence[str] = (),
+ enabled: bool = True,
+ ) -> "Observer":
+ return DefaultObserver()
+
def get_label_set(self, labels: Dict[str, str]) -> "LabelSet":
# pylint: disable=no-self-use
return DefaultLabelSet()
@@ -322,45 +400,69 @@ def get_label_set(self, labels: Dict[str, str]) -> "LabelSet":
# Once https://github.com/python/mypy/issues/7092 is resolved,
# the following type definition should be replaced with
# from opentelemetry.util.loader import ImplementationFactory
-ImplementationFactory = Callable[[Type[Meter]], Optional[Meter]]
-
-_METER = None
-_METER_FACTORY = None
+ImplementationFactory = Callable[
+ [Type[MeterProvider]], Optional[MeterProvider]
+]
+
+_METER_PROVIDER = None
+_METER_PROVIDER_FACTORY = None
+
+
+def get_meter(
+ instrumenting_module_name: str,
+ stateful: bool = True,
+ instrumenting_library_version: str = "",
+) -> "Meter":
+ """Returns a `Meter` for use by the given instrumentation library.
+ This function is a convenience wrapper for
+ opentelemetry.metrics.meter_provider().get_meter
+ """
+ return meter_provider().get_meter(
+ instrumenting_module_name, stateful, instrumenting_library_version
+ )
-def meter() -> Meter:
- """Gets the current global :class:`~.Meter` object.
+def meter_provider() -> MeterProvider:
+ """Gets the current global :class:`~.MeterProvider` object.
If there isn't one set yet, a default will be loaded.
"""
- global _METER, _METER_FACTORY # pylint:disable=global-statement
+ global _METER_PROVIDER, _METER_PROVIDER_FACTORY # pylint:disable=global-statement
- if _METER is None:
+ if _METER_PROVIDER is None:
# pylint:disable=protected-access
try:
- _METER = loader._load_impl(Meter, _METER_FACTORY) # type: ignore
+ _METER_PROVIDER = loader._load_impl(
+ MeterProvider, _METER_PROVIDER_FACTORY # type: ignore
+ )
except TypeError:
# if we raised an exception trying to instantiate an
- # abstract class, default to no-op tracer impl
- _METER = DefaultMeter()
- del _METER_FACTORY
+ # abstract class, default to no-op meter impl
+ logger.warning(
+ "Unable to instantiate MeterProvider from meter provider factory.",
+ exc_info=True,
+ )
+ _METER_PROVIDER = DefaultMeterProvider()
+ _METER_PROVIDER_FACTORY = None
- return _METER
+ return _METER_PROVIDER
-def set_preferred_meter_implementation(factory: ImplementationFactory) -> None:
- """Set the factory to be used to create the meter.
+def set_preferred_meter_provider_implementation(
+ factory: ImplementationFactory,
+) -> None:
+ """Set the factory to be used to create the meter provider.
See :mod:`opentelemetry.util.loader` for details.
This function may not be called after a meter is already loaded.
Args:
- factory: Callback that should create a new :class:`Meter` instance.
+ factory: Callback that should create a new :class:`MeterProvider` instance.
"""
- global _METER, _METER_FACTORY # pylint:disable=global-statement
+ global _METER_PROVIDER_FACTORY # pylint:disable=global-statement
- if _METER:
- raise RuntimeError("Meter already loaded.")
+ if _METER_PROVIDER:
+ raise RuntimeError("MeterProvider already loaded.")
- _METER_FACTORY = factory
+ _METER_PROVIDER_FACTORY = factory
diff --git a/opentelemetry-api/src/opentelemetry/propagators/__init__.py b/opentelemetry-api/src/opentelemetry/propagators/__init__.py
index 3974a4cb03a..f9b537cd866 100644
--- a/opentelemetry-api/src/opentelemetry/propagators/__init__.py
+++ b/opentelemetry-api/src/opentelemetry/propagators/__init__.py
@@ -12,49 +12,87 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+"""
+API for propagation of context.
+
+Example::
+
+ import flask
+ import requests
+ from opentelemetry import propagators
+
+
+ PROPAGATOR = propagators.get_global_httptextformat()
+
+
+ def get_header_from_flask_request(request, key):
+ return request.headers.get_all(key)
+
+ def set_header_into_requests_request(request: requests.Request,
+ key: str, value: str):
+ request.headers[key] = value
+
+ def example_route():
+ context = PROPAGATOR.extract(
+ get_header_from_flask_request,
+ flask.request
+ )
+ request_to_downstream = requests.Request(
+ "GET", "http://httpbin.org/get"
+ )
+ PROPAGATOR.inject(
+ set_header_into_requests_request,
+ request_to_downstream,
+ context=context
+ )
+ session = requests.Session()
+ session.send(request_to_downstream.prepare())
+
+
+.. _Propagation API Specification:
+ https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md
+"""
+
import typing
-import opentelemetry.context.propagation.httptextformat as httptextformat
import opentelemetry.trace as trace
-from opentelemetry.context.propagation.tracecontexthttptextformat import (
+from opentelemetry.context import get_current
+from opentelemetry.context.context import Context
+from opentelemetry.trace.propagation import httptextformat
+from opentelemetry.trace.propagation.tracecontexthttptextformat import (
TraceContextHTTPTextFormat,
)
-_T = typing.TypeVar("_T")
-
def extract(
- get_from_carrier: httptextformat.Getter[_T], carrier: _T
-) -> trace.SpanContext:
- """Load the parent SpanContext from values in the carrier.
-
- Using the specified HTTPTextFormatter, the propagator will
- extract a SpanContext from the carrier. If one is found,
- it will be set as the parent context of the current span.
+ get_from_carrier: httptextformat.Getter[httptextformat.HTTPTextFormatT],
+ carrier: httptextformat.HTTPTextFormatT,
+ context: typing.Optional[Context] = None,
+) -> Context:
+ """ Uses the configured propagator to extract a Context from the carrier.
Args:
get_from_carrier: a function that can retrieve zero
or more values from the carrier. In the case that
the value does not exist, return an empty list.
carrier: and object which contains values that are
- used to construct a SpanContext. This object
+ used to construct a Context. This object
must be paired with an appropriate get_from_carrier
which understands how to extract a value from it.
+ context: an optional Context to use. Defaults to current
+ context if not set.
"""
- return get_global_httptextformat().extract(get_from_carrier, carrier)
+ return get_global_httptextformat().extract(
+ get_from_carrier, carrier, context
+ )
def inject(
- tracer: trace.Tracer,
- set_in_carrier: httptextformat.Setter[_T],
- carrier: _T,
+ set_in_carrier: httptextformat.Setter[httptextformat.HTTPTextFormatT],
+ carrier: httptextformat.HTTPTextFormatT,
+ context: typing.Optional[Context] = None,
) -> None:
- """Inject values from the current context into the carrier.
-
- inject enables the propagation of values into HTTP clients or
- other objects which perform an HTTP request. Implementations
- should use the set_in_carrier method to set values on the
- carrier.
+ """ Uses the configured propagator to inject a Context into the carrier.
Args:
set_in_carrier: A setter function that can set values
@@ -62,10 +100,10 @@ def inject(
carrier: An object that contains a representation of HTTP
headers. Should be paired with set_in_carrier, which
should know how to set header values on the carrier.
+ context: an optional Context to use. Defaults to current
+ context if not set.
"""
- get_global_httptextformat().inject(
- tracer.get_current_span(), set_in_carrier, carrier
- )
+ get_global_httptextformat().inject(set_in_carrier, carrier, context)
_HTTP_TEXT_FORMAT = (
diff --git a/opentelemetry-api/src/opentelemetry/propagators/composite.py b/opentelemetry-api/src/opentelemetry/propagators/composite.py
new file mode 100644
index 00000000000..4ec953c8395
--- /dev/null
+++ b/opentelemetry-api/src/opentelemetry/propagators/composite.py
@@ -0,0 +1,69 @@
+# Copyright 2020, 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 logging
+import typing
+
+from opentelemetry.context.context import Context
+from opentelemetry.trace.propagation import httptextformat
+
+logger = logging.getLogger(__name__)
+
+
+class CompositeHTTPPropagator(httptextformat.HTTPTextFormat):
+ """ CompositeHTTPPropagator provides a mechanism for combining multiple
+ propagators into a single one.
+
+ Args:
+ propagators: the list of propagators to use
+ """
+
+ def __init__(
+ self, propagators: typing.Sequence[httptextformat.HTTPTextFormat]
+ ) -> None:
+ self._propagators = propagators
+
+ def extract(
+ self,
+ get_from_carrier: httptextformat.Getter[
+ httptextformat.HTTPTextFormatT
+ ],
+ carrier: httptextformat.HTTPTextFormatT,
+ context: typing.Optional[Context] = None,
+ ) -> Context:
+ """ Run each of the configured propagators with the given context and carrier.
+ Propagators are run in the order they are configured, if multiple
+ propagators write the same context key, the propagator later in the list
+ will override previous propagators.
+
+ See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.extract`
+ """
+ for propagator in self._propagators:
+ context = propagator.extract(get_from_carrier, carrier, context)
+ return context # type: ignore
+
+ def inject(
+ self,
+ set_in_carrier: httptextformat.Setter[httptextformat.HTTPTextFormatT],
+ carrier: httptextformat.HTTPTextFormatT,
+ context: typing.Optional[Context] = None,
+ ) -> None:
+ """ Run each of the configured propagators with the given context and carrier.
+ Propagators are run in the order they are configured, if multiple
+ propagators write the same carrier key, the propagator later in the list
+ will override previous propagators.
+
+ See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.inject`
+ """
+ for propagator in self._propagators:
+ propagator.inject(set_in_carrier, carrier, context)
diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py
index f0b1fda1b33..a6633e434a0 100644
--- a/opentelemetry-api/src/opentelemetry/trace/__init__.py
+++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py
@@ -26,7 +26,7 @@
to use the API package alone without a supporting implementation.
To get a tracer, you need to provide the package name from which you are
-calling the tracer APIs to OpenTelemetry by calling `TracerSource.get_tracer`
+calling the tracer APIs to OpenTelemetry by calling `TracerProvider.get_tracer`
with the calling module name and the version of your package.
The tracer supports creating spans that are "attached" or "detached" from the
@@ -57,13 +57,13 @@
finally:
child.end()
-Applications should generally use a single global tracer source, and use either
-implicit or explicit context propagation consistently throughout.
+Applications should generally use a single global TracerProvider, and use
+either implicit or explicit context propagation consistently throughout.
.. versionadded:: 0.1.0
.. versionchanged:: 0.3.0
- `TracerSource` was introduced and the global ``tracer`` getter was replaced
- by `tracer_source`.
+ `TracerProvider` was introduced and the global ``tracer`` getter was replaced
+ by `tracer_provider`.
"""
import abc
@@ -249,7 +249,7 @@ def __exit__(
self.end()
-class TraceOptions(int):
+class TraceFlags(int):
"""A bitmask that represents options specific to the trace.
The only supported option is the "sampled" flag (``0x01``). If set, this
@@ -265,15 +265,15 @@ class TraceOptions(int):
SAMPLED = 0x01
@classmethod
- def get_default(cls) -> "TraceOptions":
+ def get_default(cls) -> "TraceFlags":
return cls(cls.DEFAULT)
@property
def sampled(self) -> bool:
- return bool(self & TraceOptions.SAMPLED)
+ return bool(self & TraceFlags.SAMPLED)
-DEFAULT_TRACE_OPTIONS = TraceOptions.get_default()
+DEFAULT_TRACE_OPTIONS = TraceFlags.get_default()
class TraceState(typing.Dict[str, str]):
@@ -312,7 +312,7 @@ class SpanContext:
Args:
trace_id: The ID of the trace that this span belongs to.
span_id: This span's ID.
- trace_options: Trace options to propagate.
+ trace_flags: Trace options to propagate.
trace_state: Tracing-system-specific info to propagate.
"""
@@ -320,16 +320,16 @@ def __init__(
self,
trace_id: int,
span_id: int,
- trace_options: "TraceOptions" = DEFAULT_TRACE_OPTIONS,
+ trace_flags: "TraceFlags" = DEFAULT_TRACE_OPTIONS,
trace_state: "TraceState" = DEFAULT_TRACE_STATE,
) -> None:
- if trace_options is None:
- trace_options = DEFAULT_TRACE_OPTIONS
+ 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_options = trace_options
+ self.trace_flags = trace_flags
self.trace_state = trace_state
def __repr__(self) -> str:
@@ -405,7 +405,7 @@ def set_status(self, status: Status) -> None:
INVALID_SPAN = DefaultSpan(INVALID_SPAN_CONTEXT)
-class TracerSource(abc.ABC):
+class TracerProvider(abc.ABC):
@abc.abstractmethod
def get_tracer(
self,
@@ -435,8 +435,8 @@ def get_tracer(
"""
-class DefaultTracerSource(TracerSource):
- """The default TracerSource, used when no implementation is available.
+class DefaultTracerProvider(TracerProvider):
+ """The default TracerProvider, used when no implementation is available.
All operations are no-op.
"""
@@ -643,11 +643,11 @@ def use_span(
# the following type definition should be replaced with
# from opentelemetry.util.loader import ImplementationFactory
ImplementationFactory = typing.Callable[
- [typing.Type[TracerSource]], typing.Optional[TracerSource]
+ [typing.Type[TracerProvider]], typing.Optional[TracerProvider]
]
-_TRACER_SOURCE = None # type: typing.Optional[TracerSource]
-_TRACER_SOURCE_FACTORY = None # type: typing.Optional[ImplementationFactory]
+_TRACER_PROVIDER = None # type: typing.Optional[TracerProvider]
+_TRACER_PROVIDER_FACTORY = None # type: typing.Optional[ImplementationFactory]
def get_tracer(
@@ -656,55 +656,55 @@ def get_tracer(
"""Returns a `Tracer` for use by the given instrumentation library.
This function is a convenience wrapper for
- opentelemetry.trace.tracer_source().get_tracer
+ opentelemetry.trace.tracer_provider().get_tracer
"""
- return tracer_source().get_tracer(
+ return tracer_provider().get_tracer(
instrumenting_module_name, instrumenting_library_version
)
-def tracer_source() -> TracerSource:
- """Gets the current global :class:`~.TracerSource` object.
+def tracer_provider() -> TracerProvider:
+ """Gets the current global :class:`~.TracerProvider` object.
If there isn't one set yet, a default will be loaded.
"""
- global _TRACER_SOURCE, _TRACER_SOURCE_FACTORY # pylint:disable=global-statement
+ global _TRACER_PROVIDER, _TRACER_PROVIDER_FACTORY # pylint:disable=global-statement
- if _TRACER_SOURCE is None:
+ if _TRACER_PROVIDER is None:
# pylint:disable=protected-access
try:
- _TRACER_SOURCE = loader._load_impl(
- TracerSource, _TRACER_SOURCE_FACTORY # type: ignore
+ _TRACER_PROVIDER = loader._load_impl(
+ TracerProvider, _TRACER_PROVIDER_FACTORY # type: ignore
)
except TypeError:
# if we raised an exception trying to instantiate an
# abstract class, default to no-op tracer impl
logger.warning(
- "Unable to instantiate TracerSource from tracer source factory.",
+ "Unable to instantiate TracerProvider from factory.",
exc_info=True,
)
- _TRACER_SOURCE = DefaultTracerSource()
- del _TRACER_SOURCE_FACTORY
+ _TRACER_PROVIDER = DefaultTracerProvider()
+ del _TRACER_PROVIDER_FACTORY
- return _TRACER_SOURCE
+ return _TRACER_PROVIDER
-def set_preferred_tracer_source_implementation(
+def set_preferred_tracer_provider_implementation(
factory: ImplementationFactory,
) -> None:
- """Set the factory to be used to create the tracer source.
+ """Set the factory to be used to create the global TracerProvider.
See :mod:`opentelemetry.util.loader` for details.
This function may not be called after a tracer is already loaded.
Args:
- factory: Callback that should create a new :class:`TracerSource`
+ factory: Callback that should create a new :class:`TracerProvider`
instance.
"""
- global _TRACER_SOURCE_FACTORY # pylint:disable=global-statement
+ global _TRACER_PROVIDER_FACTORY # pylint:disable=global-statement
- if _TRACER_SOURCE:
- raise RuntimeError("TracerSource already loaded.")
+ if _TRACER_PROVIDER:
+ raise RuntimeError("TracerProvider already loaded.")
- _TRACER_SOURCE_FACTORY = factory
+ _TRACER_PROVIDER_FACTORY = factory
diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py
index 881a74287a2..90e7f9dcb3c 100644
--- a/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py
+++ b/opentelemetry-api/src/opentelemetry/trace/propagation/__init__.py
@@ -13,7 +13,22 @@
# limitations under the License.
from typing import Optional
-from opentelemetry.trace import INVALID_SPAN_CONTEXT, Span, SpanContext
+from opentelemetry import trace as trace_api
+from opentelemetry.context import get_value, set_value
+from opentelemetry.context.context import Context
-_SPAN_CONTEXT_KEY = "extracted-span-context"
SPAN_KEY = "current-span"
+
+
+def set_span_in_context(
+ span: trace_api.Span, context: Optional[Context] = None
+) -> Context:
+ ctx = set_value(SPAN_KEY, span, context=context)
+ return ctx
+
+
+def get_span_from_context(context: Optional[Context] = None) -> trace_api.Span:
+ span = get_value(SPAN_KEY, context=context)
+ if not isinstance(span, trace_api.Span):
+ return trace_api.INVALID_SPAN
+ return span
diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py b/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py
similarity index 50%
rename from opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py
rename to opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py
index b64a298c410..500014d7384 100644
--- a/opentelemetry-api/src/opentelemetry/context/propagation/httptextformat.py
+++ b/opentelemetry-api/src/opentelemetry/trace/propagation/httptextformat.py
@@ -15,89 +15,59 @@
import abc
import typing
-from opentelemetry.trace import Span, SpanContext
+from opentelemetry.context.context import Context
-_T = typing.TypeVar("_T")
+HTTPTextFormatT = typing.TypeVar("HTTPTextFormatT")
-Setter = typing.Callable[[_T, str, str], None]
-Getter = typing.Callable[[_T, str], typing.List[str]]
+Setter = typing.Callable[[HTTPTextFormatT, str, str], None]
+Getter = typing.Callable[[HTTPTextFormatT, str], typing.List[str]]
class HTTPTextFormat(abc.ABC):
- """API for propagation of span context via headers.
-
- This class provides an interface that enables extracting and injecting
- span context into headers of HTTP requests. HTTP frameworks and clients
+ """This class provides an interface that enables extracting and injecting
+ context into headers of HTTP requests. HTTP frameworks and clients
can integrate with HTTPTextFormat by providing the object containing the
headers, and a getter and setter function for the extraction and
injection of values, respectively.
- Example::
-
- import flask
- import requests
- from opentelemetry.context.propagation import HTTPTextFormat
-
- PROPAGATOR = HTTPTextFormat()
-
-
-
- def get_header_from_flask_request(request, key):
- return request.headers.get_all(key)
-
- def set_header_into_requests_request(request: requests.Request,
- key: str, value: str):
- request.headers[key] = value
-
- def example_route():
- span_context = PROPAGATOR.extract(
- get_header_from_flask_request,
- flask.request
- )
- request_to_downstream = requests.Request(
- "GET", "http://httpbin.org/get"
- )
- PROPAGATOR.inject(
- span_context,
- set_header_into_requests_request,
- request_to_downstream
- )
- session = requests.Session()
- session.send(request_to_downstream.prepare())
-
-
- .. _Propagation API Specification:
- https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-propagators.md
"""
@abc.abstractmethod
def extract(
- self, get_from_carrier: Getter[_T], carrier: _T
- ) -> SpanContext:
- """Create a SpanContext from values in the carrier.
+ self,
+ get_from_carrier: Getter[HTTPTextFormatT],
+ carrier: HTTPTextFormatT,
+ context: typing.Optional[Context] = None,
+ ) -> Context:
+ """Create a Context from values in the carrier.
The extract function should retrieve values from the carrier
object using get_from_carrier, and use values to populate a
- SpanContext value and return it.
+ Context value and return it.
Args:
get_from_carrier: a function that can retrieve zero
or more values from the carrier. In the case that
the value does not exist, return an empty list.
carrier: and object which contains values that are
- used to construct a SpanContext. This object
+ used to construct a Context. This object
must be paired with an appropriate get_from_carrier
which understands how to extract a value from it.
+ context: an optional Context to use. Defaults to current
+ context if not set.
Returns:
- A SpanContext with configuration found in the carrier.
+ A Context with configuration found in the carrier.
"""
@abc.abstractmethod
def inject(
- self, span: Span, set_in_carrier: Setter[_T], carrier: _T
+ self,
+ set_in_carrier: Setter[HTTPTextFormatT],
+ carrier: HTTPTextFormatT,
+ context: typing.Optional[Context] = None,
) -> None:
- """Inject values from a Span into a carrier.
+ """Inject values from a Context into a carrier.
inject enables the propagation of values into HTTP clients or
other objects which perform an HTTP request. Implementations
@@ -105,11 +75,12 @@ def inject(
carrier.
Args:
- context: The SpanContext to read values from.
set_in_carrier: A setter function that can set values
on the carrier.
carrier: An object that a place to define HTTP headers.
Should be paired with set_in_carrier, which should
know how to set header values on the carrier.
+ context: an optional Context to use. Defaults to current
+ context if not set.
"""
diff --git a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py
similarity index 69%
rename from opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py
rename to opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py
index 6f50f008394..28db4e45575 100644
--- a/opentelemetry-api/src/opentelemetry/context/propagation/tracecontexthttptextformat.py
+++ b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontexthttptextformat.py
@@ -16,9 +16,12 @@
import typing
import opentelemetry.trace as trace
-from opentelemetry.context.propagation import httptextformat
-
-_T = typing.TypeVar("_T")
+from opentelemetry.context.context import Context
+from opentelemetry.trace.propagation import (
+ get_span_from_context,
+ httptextformat,
+ set_span_in_context,
+)
# Keys and values are strings of up to 256 printable US-ASCII characters.
# Implementations should conform to the `W3C Trace Context - Tracestate`_
@@ -59,71 +62,80 @@ class TraceContextHTTPTextFormat(httptextformat.HTTPTextFormat):
)
_TRACEPARENT_HEADER_FORMAT_RE = re.compile(_TRACEPARENT_HEADER_FORMAT)
- @classmethod
def extract(
- cls, get_from_carrier: httptextformat.Getter[_T], carrier: _T
- ) -> trace.SpanContext:
- """Extracts a valid SpanContext from the carrier.
+ self,
+ get_from_carrier: httptextformat.Getter[
+ httptextformat.HTTPTextFormatT
+ ],
+ carrier: httptextformat.HTTPTextFormatT,
+ context: typing.Optional[Context] = None,
+ ) -> Context:
+ """Extracts SpanContext from the carrier.
+
+ See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.extract`
"""
- header = get_from_carrier(carrier, cls._TRACEPARENT_HEADER_NAME)
+ header = get_from_carrier(carrier, self._TRACEPARENT_HEADER_NAME)
if not header:
- return trace.INVALID_SPAN_CONTEXT
+ return set_span_in_context(trace.INVALID_SPAN, context)
- match = re.search(cls._TRACEPARENT_HEADER_FORMAT_RE, header[0])
+ match = re.search(self._TRACEPARENT_HEADER_FORMAT_RE, header[0])
if not match:
- return trace.INVALID_SPAN_CONTEXT
+ return set_span_in_context(trace.INVALID_SPAN, context)
version = match.group(1)
trace_id = match.group(2)
span_id = match.group(3)
- trace_options = match.group(4)
+ trace_flags = match.group(4)
if trace_id == "0" * 32 or span_id == "0" * 16:
- return trace.INVALID_SPAN_CONTEXT
+ return set_span_in_context(trace.INVALID_SPAN, context)
if version == "00":
if match.group(5):
- return trace.INVALID_SPAN_CONTEXT
+ return set_span_in_context(trace.INVALID_SPAN, context)
if version == "ff":
- return trace.INVALID_SPAN_CONTEXT
+ return set_span_in_context(trace.INVALID_SPAN, context)
tracestate_headers = get_from_carrier(
- carrier, cls._TRACESTATE_HEADER_NAME
+ carrier, self._TRACESTATE_HEADER_NAME
)
tracestate = _parse_tracestate(tracestate_headers)
span_context = trace.SpanContext(
trace_id=int(trace_id, 16),
span_id=int(span_id, 16),
- trace_options=trace.TraceOptions(trace_options),
+ trace_flags=trace.TraceFlags(trace_flags),
trace_state=tracestate,
)
+ return set_span_in_context(trace.DefaultSpan(span_context), context)
- return span_context
-
- @classmethod
def inject(
- cls,
- span: trace.Span,
- set_in_carrier: httptextformat.Setter[_T],
- carrier: _T,
+ self,
+ set_in_carrier: httptextformat.Setter[httptextformat.HTTPTextFormatT],
+ carrier: httptextformat.HTTPTextFormatT,
+ context: typing.Optional[Context] = None,
) -> None:
+ """Injects SpanContext into the carrier.
- context = span.get_context()
+ See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.inject`
+ """
+ span_context = get_span_from_context(context).get_context()
- if context == trace.INVALID_SPAN_CONTEXT:
+ if span_context == trace.INVALID_SPAN_CONTEXT:
return
traceparent_string = "00-{:032x}-{:016x}-{:02x}".format(
- context.trace_id, context.span_id, context.trace_options
+ span_context.trace_id,
+ span_context.span_id,
+ span_context.trace_flags,
)
set_in_carrier(
- carrier, cls._TRACEPARENT_HEADER_NAME, traceparent_string
+ carrier, self._TRACEPARENT_HEADER_NAME, traceparent_string
)
- if context.trace_state:
- tracestate_string = _format_tracestate(context.trace_state)
+ if span_context.trace_state:
+ tracestate_string = _format_tracestate(span_context.trace_state)
set_in_carrier(
- carrier, cls._TRACESTATE_HEADER_NAME, tracestate_string
+ carrier, self._TRACESTATE_HEADER_NAME, tracestate_string
)
diff --git a/opentelemetry-api/src/opentelemetry/trace/sampling.py b/opentelemetry-api/src/opentelemetry/trace/sampling.py
index 503c2e03eb0..c398efbf86c 100644
--- a/opentelemetry-api/src/opentelemetry/trace/sampling.py
+++ b/opentelemetry-api/src/opentelemetry/trace/sampling.py
@@ -113,7 +113,7 @@ def should_sample(
links: Sequence["Link"] = (),
) -> "Decision":
if parent_context is not None:
- return Decision(parent_context.trace_options.sampled)
+ return Decision(parent_context.trace_flags.sampled)
return Decision(trace_id & self.TRACE_ID_LIMIT < self.bound)
diff --git a/opentelemetry-api/src/opentelemetry/util/__init__.py b/opentelemetry-api/src/opentelemetry/util/__init__.py
index cbf36d4c05a..9bfc79df21c 100644
--- a/opentelemetry-api/src/opentelemetry/util/__init__.py
+++ b/opentelemetry-api/src/opentelemetry/util/__init__.py
@@ -1,3 +1,16 @@
+# Copyright 2020, 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
# Since we want API users to be able to provide timestamps,
diff --git a/opentelemetry-api/src/opentelemetry/util/loader.py b/opentelemetry-api/src/opentelemetry/util/loader.py
index b65c822ab9f..eeda2b3e7f7 100644
--- a/opentelemetry-api/src/opentelemetry/util/loader.py
+++ b/opentelemetry-api/src/opentelemetry/util/loader.py
@@ -16,7 +16,7 @@
"""
The OpenTelemetry loader module is mainly used internally to load the
implementation for global objects like
-:func:`opentelemetry.trace.tracer_source`.
+:func:`opentelemetry.trace.tracer_provider`.
.. _loader-factory:
@@ -28,7 +28,7 @@ def my_factory_for_t(api_type: typing.Type[T]) -> typing.Optional[T]:
That function is called with e.g., the type of the global object it should
create as an argument (e.g. the type object
-:class:`opentelemetry.trace.TracerSource`) and should return an instance of that type
+:class:`opentelemetry.trace.TracerProvider`) and should return an instance of that type
(such that ``instanceof(my_factory_for_t(T), T)`` is true). Alternatively, it
may return ``None`` to indicate that the no-op default should be used.
@@ -37,14 +37,14 @@ def my_factory_for_t(api_type: typing.Type[T]) -> typing.Optional[T]:
1. If the environment variable
:samp:`OPENTELEMETRY_PYTHON_IMPLEMENTATION_{getter-name}` (e.g.,
- ``OPENTELEMETRY_PYTHON_IMPLEMENTATION_TRACERSOURCE``) is set to an
+ ``OPENTELEMETRY_PYTHON_IMPLEMENTATION_TRACERPROVIDER``) is set to an
nonempty value, an attempt is made to import a module with that name and
use a factory function named ``get_opentelemetry_implementation`` in it.
2. Otherwise, the same is tried with the environment variable
``OPENTELEMETRY_PYTHON_IMPLEMENTATION_DEFAULT``.
3. Otherwise, if a :samp:`set_preferred_{}_implementation` was
called (e.g.
- :func:`opentelemetry.trace.set_preferred_tracer_source_implementation`),
+ :func:`opentelemetry.trace.set_preferred_tracer_provider_implementation`),
the callback set there is used (that is, the environment variables
override the callback set in code).
4. Otherwise, if :func:`set_preferred_default_implementation` was called,
diff --git a/opentelemetry-api/src/opentelemetry/util/version.py b/opentelemetry-api/src/opentelemetry/util/version.py
index 2f792fff802..d13bf967481 100644
--- a/opentelemetry-api/src/opentelemetry/util/version.py
+++ b/opentelemetry-api/src/opentelemetry/util/version.py
@@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-__version__ = "0.4.dev0"
+__version__ = "0.5.dev0"
diff --git a/opentelemetry-api/tests/context/base_context.py b/opentelemetry-api/tests/context/base_context.py
new file mode 100644
index 00000000000..66e6df97a2d
--- /dev/null
+++ b/opentelemetry-api/tests/context/base_context.py
@@ -0,0 +1,77 @@
+# Copyright 2020, 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 logging import ERROR
+
+from opentelemetry import context
+
+
+def do_work() -> None:
+ context.attach(context.set_value("say", "bar"))
+
+
+class ContextTestCases:
+ class BaseTest(unittest.TestCase):
+ def setUp(self) -> None:
+ self.previous_context = context.get_current()
+
+ def tearDown(self) -> None:
+ context.attach(self.previous_context)
+
+ def test_context(self):
+ self.assertIsNone(context.get_value("say"))
+ empty = context.get_current()
+ second = context.set_value("say", "foo")
+
+ self.assertEqual(context.get_value("say", context=second), "foo")
+
+ do_work()
+ self.assertEqual(context.get_value("say"), "bar")
+ third = context.get_current()
+
+ self.assertIsNone(context.get_value("say", context=empty))
+ self.assertEqual(context.get_value("say", context=second), "foo")
+ self.assertEqual(context.get_value("say", context=third), "bar")
+
+ def test_set_value(self):
+ first = context.set_value("a", "yyy")
+ second = context.set_value("a", "zzz")
+ third = context.set_value("a", "---", first)
+ self.assertEqual("yyy", context.get_value("a", context=first))
+ self.assertEqual("zzz", context.get_value("a", context=second))
+ self.assertEqual("---", context.get_value("a", context=third))
+ self.assertEqual(None, context.get_value("a"))
+
+ def test_attach(self):
+ context.attach(context.set_value("a", "yyy"))
+
+ token = context.attach(context.set_value("a", "zzz"))
+ self.assertEqual("zzz", context.get_value("a"))
+
+ context.detach(token)
+ self.assertEqual("yyy", context.get_value("a"))
+
+ with self.assertLogs(level=ERROR):
+ context.detach("some garbage")
+
+ def test_detach_out_of_order(self):
+ t1 = context.attach(context.set_value("c", 1))
+ self.assertEqual(context.get_current(), {"c": 1})
+ t2 = context.attach(context.set_value("c", 2))
+ self.assertEqual(context.get_current(), {"c": 2})
+ context.detach(t1)
+ self.assertEqual(context.get_current(), {})
+ context.detach(t2)
+ self.assertEqual(context.get_current(), {"c": 1})
diff --git a/opentelemetry-api/tests/context/test_context.py b/opentelemetry-api/tests/context/test_context.py
index 2536e5149be..8942a333ed6 100644
--- a/opentelemetry-api/tests/context/test_context.py
+++ b/opentelemetry-api/tests/context/test_context.py
@@ -19,12 +19,12 @@
def do_work() -> None:
- context.set_current(context.set_value("say", "bar"))
+ context.attach(context.set_value("say", "bar"))
class TestContext(unittest.TestCase):
def setUp(self):
- context.set_current(Context())
+ context.attach(Context())
def test_context(self):
self.assertIsNone(context.get_value("say"))
@@ -55,11 +55,10 @@ def test_context_is_immutable(self):
context.get_current()["test"] = "cant-change-immutable"
def test_set_current(self):
- context.set_current(context.set_value("a", "yyy"))
+ context.attach(context.set_value("a", "yyy"))
- old_context = context.set_current(context.set_value("a", "zzz"))
- self.assertEqual("yyy", context.get_value("a", context=old_context))
+ token = context.attach(context.set_value("a", "zzz"))
self.assertEqual("zzz", context.get_value("a"))
- context.set_current(old_context)
+ context.detach(token)
self.assertEqual("yyy", context.get_value("a"))
diff --git a/opentelemetry-api/tests/context/test_contextvars_context.py b/opentelemetry-api/tests/context/test_contextvars_context.py
index ebc15d6d9a3..d19ac5ca126 100644
--- a/opentelemetry-api/tests/context/test_contextvars_context.py
+++ b/opentelemetry-api/tests/context/test_contextvars_context.py
@@ -17,6 +17,8 @@
from opentelemetry import context
+from .base_context import ContextTestCases
+
try:
import contextvars # pylint: disable=unused-import
from opentelemetry.context.contextvars_context import (
@@ -26,43 +28,14 @@
raise unittest.SkipTest("contextvars not available")
-def do_work() -> None:
- context.set_current(context.set_value("say", "bar"))
-
-
-class TestContextVarsContext(unittest.TestCase):
- def setUp(self):
- self.previous_context = context.get_current()
-
- def tearDown(self):
- context.set_current(self.previous_context)
-
- @patch(
- "opentelemetry.context._RUNTIME_CONTEXT", ContextVarsRuntimeContext() # type: ignore
- )
- def test_context(self):
- self.assertIsNone(context.get_value("say"))
- empty = context.get_current()
- second = context.set_value("say", "foo")
-
- self.assertEqual(context.get_value("say", context=second), "foo")
-
- do_work()
- self.assertEqual(context.get_value("say"), "bar")
- third = context.get_current()
+class TestContextVarsContext(ContextTestCases.BaseTest):
+ def setUp(self) -> None:
+ super(TestContextVarsContext, self).setUp()
+ self.mock_runtime = patch.object(
+ context, "_RUNTIME_CONTEXT", ContextVarsRuntimeContext(),
+ )
+ self.mock_runtime.start()
- self.assertIsNone(context.get_value("say", context=empty))
- self.assertEqual(context.get_value("say", context=second), "foo")
- self.assertEqual(context.get_value("say", context=third), "bar")
-
- @patch(
- "opentelemetry.context._RUNTIME_CONTEXT", ContextVarsRuntimeContext() # type: ignore
- )
- def test_set_value(self):
- first = context.set_value("a", "yyy")
- second = context.set_value("a", "zzz")
- third = context.set_value("a", "---", first)
- self.assertEqual("yyy", context.get_value("a", context=first))
- self.assertEqual("zzz", context.get_value("a", context=second))
- self.assertEqual("---", context.get_value("a", context=third))
- self.assertEqual(None, context.get_value("a"))
+ def tearDown(self) -> None:
+ super(TestContextVarsContext, self).tearDown()
+ self.mock_runtime.stop()
diff --git a/opentelemetry-api/tests/context/test_threadlocal_context.py b/opentelemetry-api/tests/context/test_threadlocal_context.py
index aca6b69de72..342163020ed 100644
--- a/opentelemetry-api/tests/context/test_threadlocal_context.py
+++ b/opentelemetry-api/tests/context/test_threadlocal_context.py
@@ -12,50 +12,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import unittest
from unittest.mock import patch
from opentelemetry import context
from opentelemetry.context.threadlocal_context import ThreadLocalRuntimeContext
+from .base_context import ContextTestCases
-def do_work() -> None:
- context.set_current(context.set_value("say", "bar"))
+class TestThreadLocalContext(ContextTestCases.BaseTest):
+ def setUp(self) -> None:
+ super(TestThreadLocalContext, self).setUp()
+ self.mock_runtime = patch.object(
+ context, "_RUNTIME_CONTEXT", ThreadLocalRuntimeContext(),
+ )
+ self.mock_runtime.start()
-class TestThreadLocalContext(unittest.TestCase):
- def setUp(self):
- self.previous_context = context.get_current()
-
- def tearDown(self):
- context.set_current(self.previous_context)
-
- @patch(
- "opentelemetry.context._RUNTIME_CONTEXT", ThreadLocalRuntimeContext() # type: ignore
- )
- def test_context(self):
- self.assertIsNone(context.get_value("say"))
- empty = context.get_current()
- second = context.set_value("say", "foo")
-
- self.assertEqual(context.get_value("say", context=second), "foo")
-
- do_work()
- self.assertEqual(context.get_value("say"), "bar")
- third = context.get_current()
-
- self.assertIsNone(context.get_value("say", context=empty))
- self.assertEqual(context.get_value("say", context=second), "foo")
- self.assertEqual(context.get_value("say", context=third), "bar")
-
- @patch(
- "opentelemetry.context._RUNTIME_CONTEXT", ThreadLocalRuntimeContext() # type: ignore
- )
- def test_set_value(self):
- first = context.set_value("a", "yyy")
- second = context.set_value("a", "zzz")
- third = context.set_value("a", "---", first)
- self.assertEqual("yyy", context.get_value("a", context=first))
- self.assertEqual("zzz", context.get_value("a", context=second))
- self.assertEqual("---", context.get_value("a", context=third))
- self.assertEqual(None, context.get_value("a"))
+ def tearDown(self) -> None:
+ super(TestThreadLocalContext, self).tearDown()
+ self.mock_runtime.stop()
diff --git a/opentelemetry-api/tests/metrics/test_metrics.py b/opentelemetry-api/tests/metrics/test_metrics.py
index 3ec0f81c718..45913ca6720 100644
--- a/opentelemetry-api/tests/metrics/test_metrics.py
+++ b/opentelemetry-api/tests/metrics/test_metrics.py
@@ -1,4 +1,4 @@
-# Copyright 2019, OpenTelemetry Authors
+# Copyright 2020, OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,31 +13,11 @@
# limitations under the License.
import unittest
-from contextlib import contextmanager
-from unittest import mock
from opentelemetry import metrics
# pylint: disable=no-self-use
-class TestMeter(unittest.TestCase):
- def setUp(self):
- self.meter = metrics.DefaultMeter()
-
- def test_record_batch(self):
- counter = metrics.Counter()
- label_set = metrics.LabelSet()
- self.meter.record_batch(label_set, ((counter, 1),))
-
- def test_create_metric(self):
- metric = self.meter.create_metric("", "", "", float, metrics.Counter)
- self.assertIsInstance(metric, metrics.DefaultMetric)
-
- def test_get_label_set(self):
- metric = self.meter.get_label_set({})
- self.assertIsInstance(metric, metrics.DefaultLabelSet)
-
-
class TestMetrics(unittest.TestCase):
def test_default(self):
default = metrics.DefaultMetric()
@@ -56,17 +36,6 @@ def test_counter_add(self):
label_set = metrics.LabelSet()
counter.add(1, label_set)
- def test_gauge(self):
- gauge = metrics.Gauge()
- label_set = metrics.LabelSet()
- handle = gauge.get_handle(label_set)
- self.assertIsInstance(handle, metrics.GaugeHandle)
-
- def test_gauge_set(self):
- gauge = metrics.Gauge()
- label_set = metrics.LabelSet()
- gauge.set(1, label_set)
-
def test_measure(self):
measure = metrics.Measure()
label_set = metrics.LabelSet()
@@ -85,41 +54,6 @@ def test_counter_handle(self):
handle = metrics.CounterHandle()
handle.add(1)
- def test_gauge_handle(self):
- handle = metrics.GaugeHandle()
- handle.set(1)
-
def test_measure_handle(self):
handle = metrics.MeasureHandle()
handle.record(1)
-
-
-@contextmanager
-# type: ignore
-def patch_metrics_globals(meter=None, meter_factory=None):
- """Mock metrics._METER and metrics._METER_FACTORY.
-
- This prevents previous changes to these values from affecting the code in
- this scope, and prevents changes in this scope from leaking out and
- affecting other tests.
- """
- with mock.patch("opentelemetry.metrics._METER", meter):
- with mock.patch("opentelemetry.metrics._METER_FACTORY", meter_factory):
- yield
-
-
-class TestGlobals(unittest.TestCase):
- def test_meter_default_factory(self):
- """Check that the default meter is a DefaultMeter."""
- with patch_metrics_globals():
- meter = metrics.meter()
- self.assertIsInstance(meter, metrics.DefaultMeter)
- # Check that we don't create a new instance on each call
- self.assertIs(meter, metrics.meter())
-
- def test_meter_custom_factory(self):
- """Check that we use the provided factory for custom global meters."""
- mock_meter = mock.Mock(metrics.Meter)
- with patch_metrics_globals(meter_factory=lambda _: mock_meter):
- meter = metrics.meter()
- self.assertIs(meter, mock_meter)
diff --git a/opentelemetry-api/tests/mypysmoke.py b/opentelemetry-api/tests/mypysmoke.py
index 3f652adca15..bbbda93ef29 100644
--- a/opentelemetry-api/tests/mypysmoke.py
+++ b/opentelemetry-api/tests/mypysmoke.py
@@ -15,5 +15,5 @@
import opentelemetry.trace
-def dummy_check_mypy_returntype() -> opentelemetry.trace.TracerSource:
- return opentelemetry.trace.tracer_source()
+def dummy_check_mypy_returntype() -> opentelemetry.trace.TracerProvider:
+ return opentelemetry.trace.tracer_provider()
diff --git a/opentelemetry-api/tests/propagators/test_composite.py b/opentelemetry-api/tests/propagators/test_composite.py
new file mode 100644
index 00000000000..09ac0ecf689
--- /dev/null
+++ b/opentelemetry-api/tests/propagators/test_composite.py
@@ -0,0 +1,107 @@
+# Copyright 2020, 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.mock import Mock
+
+from opentelemetry.propagators.composite import CompositeHTTPPropagator
+
+
+def get_as_list(dict_object, key):
+ value = dict_object.get(key)
+ return [value] if value is not None else []
+
+
+def mock_inject(name, value="data"):
+ def wrapped(setter, carrier=None, context=None):
+ carrier[name] = value
+
+ return wrapped
+
+
+def mock_extract(name, value="context"):
+ def wrapped(getter, carrier=None, context=None):
+ new_context = context.copy()
+ new_context[name] = value
+ return new_context
+
+ return wrapped
+
+
+class TestCompositePropagator(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.mock_propagator_0 = Mock(
+ inject=mock_inject("mock-0"), extract=mock_extract("mock-0")
+ )
+ cls.mock_propagator_1 = Mock(
+ inject=mock_inject("mock-1"), extract=mock_extract("mock-1")
+ )
+ cls.mock_propagator_2 = Mock(
+ inject=mock_inject("mock-0", value="data2"),
+ extract=mock_extract("mock-0", value="context2"),
+ )
+
+ def test_no_propagators(self):
+ propagator = CompositeHTTPPropagator([])
+ new_carrier = {}
+ propagator.inject(dict.__setitem__, carrier=new_carrier)
+ self.assertEqual(new_carrier, {})
+
+ context = propagator.extract(
+ get_as_list, carrier=new_carrier, context={}
+ )
+ self.assertEqual(context, {})
+
+ def test_single_propagator(self):
+ propagator = CompositeHTTPPropagator([self.mock_propagator_0])
+
+ new_carrier = {}
+ propagator.inject(dict.__setitem__, carrier=new_carrier)
+ self.assertEqual(new_carrier, {"mock-0": "data"})
+
+ context = propagator.extract(
+ get_as_list, carrier=new_carrier, context={}
+ )
+ self.assertEqual(context, {"mock-0": "context"})
+
+ def test_multiple_propagators(self):
+ propagator = CompositeHTTPPropagator(
+ [self.mock_propagator_0, self.mock_propagator_1]
+ )
+
+ new_carrier = {}
+ propagator.inject(dict.__setitem__, carrier=new_carrier)
+ self.assertEqual(new_carrier, {"mock-0": "data", "mock-1": "data"})
+
+ context = propagator.extract(
+ get_as_list, carrier=new_carrier, context={}
+ )
+ self.assertEqual(context, {"mock-0": "context", "mock-1": "context"})
+
+ def test_multiple_propagators_same_key(self):
+ # test that when multiple propagators extract/inject the same
+ # key, the later propagator values are extracted/injected
+ propagator = CompositeHTTPPropagator(
+ [self.mock_propagator_0, self.mock_propagator_2]
+ )
+
+ new_carrier = {}
+ propagator.inject(dict.__setitem__, carrier=new_carrier)
+ self.assertEqual(new_carrier, {"mock-0": "data2"})
+
+ context = propagator.extract(
+ get_as_list, carrier=new_carrier, context={}
+ )
+ self.assertEqual(context, {"mock-0": "context2"})
diff --git a/opentelemetry-api/tests/test_implementation.py b/opentelemetry-api/tests/test_implementation.py
index c7d1d453a1c..7271eb51399 100644
--- a/opentelemetry-api/tests/test_implementation.py
+++ b/opentelemetry-api/tests/test_implementation.py
@@ -1,4 +1,4 @@
-# Copyright 2019, OpenTelemetry Authors
+# Copyright 2020, OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
# limitations under the License.
import unittest
+from unittest import mock
from opentelemetry import metrics, trace
@@ -25,14 +26,16 @@ class TestAPIOnlyImplementation(unittest.TestCase):
https://github.com/open-telemetry/opentelemetry-python/issues/142
"""
+ # TRACER
+
def test_tracer(self):
with self.assertRaises(TypeError):
# pylint: disable=abstract-class-instantiated
- trace.TracerSource() # type:ignore
+ trace.TracerProvider() # type:ignore
def test_default_tracer(self):
- tracer_source = trace.DefaultTracerSource()
- tracer = tracer_source.get_tracer(__name__)
+ tracer_provider = trace.DefaultTracerProvider()
+ tracer = tracer_provider.get_tracer(__name__)
with tracer.start_span("test") as span:
self.assertEqual(span.get_context(), trace.INVALID_SPAN_CONTEXT)
self.assertEqual(span, trace.INVALID_SPAN)
@@ -54,12 +57,37 @@ def test_default_span(self):
self.assertEqual(span.get_context(), trace.INVALID_SPAN_CONTEXT)
self.assertIs(span.is_recording_events(), False)
+ # METER
+
def test_meter(self):
with self.assertRaises(TypeError):
# pylint: disable=abstract-class-instantiated
metrics.Meter() # type:ignore
def test_default_meter(self):
+ meter_provider = metrics.DefaultMeterProvider()
+ meter = meter_provider.get_meter(__name__)
+ self.assertIsInstance(meter, metrics.DefaultMeter)
+
+ # pylint: disable=no-self-use
+ def test_record_batch(self):
+ meter = metrics.DefaultMeter()
+ counter = metrics.Counter()
+ label_set = metrics.LabelSet()
+ meter.record_batch(label_set, ((counter, 1),))
+
+ def test_create_metric(self):
meter = metrics.DefaultMeter()
metric = meter.create_metric("", "", "", float, metrics.Counter)
self.assertIsInstance(metric, metrics.DefaultMetric)
+
+ def test_register_observer(self):
+ meter = metrics.DefaultMeter()
+ callback = mock.Mock()
+ observer = meter.register_observer(callback, "", "", "", int, (), True)
+ self.assertIsInstance(observer, metrics.DefaultObserver)
+
+ def test_get_label_set(self):
+ meter = metrics.DefaultMeter()
+ label_set = meter.get_label_set({})
+ self.assertIsInstance(label_set, metrics.DefaultLabelSet)
diff --git a/opentelemetry-api/tests/test_loader.py b/opentelemetry-api/tests/test_loader.py
index eda241615fe..76575df705a 100644
--- a/opentelemetry-api/tests/test_loader.py
+++ b/opentelemetry-api/tests/test_loader.py
@@ -21,10 +21,10 @@
from opentelemetry import trace
from opentelemetry.util import loader
-DUMMY_TRACER_SOURCE = None
+DUMMY_TRACER_PROVIDER = None
-class DummyTracerSource(trace.TracerSource):
+class DummyTracerProvider(trace.TracerProvider):
def get_tracer(
self,
instrumenting_module_name: str,
@@ -34,10 +34,10 @@ def get_tracer(
def get_opentelemetry_implementation(type_):
- global DUMMY_TRACER_SOURCE # pylint:disable=global-statement
- assert type_ is trace.TracerSource
- DUMMY_TRACER_SOURCE = DummyTracerSource()
- return DUMMY_TRACER_SOURCE
+ global DUMMY_TRACER_PROVIDER # pylint:disable=global-statement
+ assert type_ is trace.TracerProvider
+ DUMMY_TRACER_PROVIDER = DummyTracerProvider()
+ return DUMMY_TRACER_PROVIDER
# pylint:disable=redefined-outer-name,protected-access,unidiomatic-typecheck
@@ -48,31 +48,31 @@ def setUp(self):
reload(loader)
reload(trace)
- # Need to reload self, otherwise DummyTracerSource will have the wrong
+ # Need to reload self, otherwise DummyTracerProvider will have the wrong
# base class after reloading `trace`.
reload(sys.modules[__name__])
def test_get_default(self):
- tracer_source = trace.tracer_source()
- self.assertIs(type(tracer_source), trace.DefaultTracerSource)
+ tracer_provider = trace.tracer_provider()
+ self.assertIs(type(tracer_provider), trace.DefaultTracerProvider)
def test_preferred_impl(self):
- trace.set_preferred_tracer_source_implementation(
+ trace.set_preferred_tracer_provider_implementation(
get_opentelemetry_implementation
)
- tracer_source = trace.tracer_source()
- self.assertIs(tracer_source, DUMMY_TRACER_SOURCE)
+ tracer_provider = trace.tracer_provider()
+ self.assertIs(tracer_provider, DUMMY_TRACER_PROVIDER)
# NOTE: We use do_* + *_ methods because subtest wouldn't run setUp,
# which we require here.
def do_test_preferred_impl(self, setter: Callable[[Any], Any]) -> None:
setter(get_opentelemetry_implementation)
- tracer_source = trace.tracer_source()
- self.assertIs(tracer_source, DUMMY_TRACER_SOURCE)
+ tracer_provider = trace.tracer_provider()
+ self.assertIs(tracer_provider, DUMMY_TRACER_PROVIDER)
def test_preferred_impl_with_tracer(self):
self.do_test_preferred_impl(
- trace.set_preferred_tracer_source_implementation
+ trace.set_preferred_tracer_provider_implementation
)
def test_preferred_impl_with_default(self):
@@ -81,16 +81,16 @@ def test_preferred_impl_with_default(self):
)
def test_try_set_again(self):
- self.assertTrue(trace.tracer_source())
- # Try setting after the tracer_source has already been created:
+ self.assertTrue(trace.tracer_provider())
+ # Try setting after the tracer_provider has already been created:
with self.assertRaises(RuntimeError) as einfo:
- trace.set_preferred_tracer_source_implementation(
+ trace.set_preferred_tracer_provider_implementation(
get_opentelemetry_implementation
)
self.assertIn("already loaded", str(einfo.exception))
def do_test_get_envvar(self, envvar_suffix: str) -> None:
- global DUMMY_TRACER_SOURCE # pylint:disable=global-statement
+ global DUMMY_TRACER_PROVIDER # pylint:disable=global-statement
# Test is not runnable with this!
self.assertFalse(sys.flags.ignore_environment)
@@ -98,15 +98,15 @@ def do_test_get_envvar(self, envvar_suffix: str) -> None:
envname = "OPENTELEMETRY_PYTHON_IMPLEMENTATION_" + envvar_suffix
os.environ[envname] = __name__
try:
- tracer_source = trace.tracer_source()
- self.assertIs(tracer_source, DUMMY_TRACER_SOURCE)
+ tracer_provider = trace.tracer_provider()
+ self.assertIs(tracer_provider, DUMMY_TRACER_PROVIDER)
finally:
- DUMMY_TRACER_SOURCE = None
+ DUMMY_TRACER_PROVIDER = None
del os.environ[envname]
- self.assertIs(type(tracer_source), DummyTracerSource)
+ self.assertIs(type(tracer_provider), DummyTracerProvider)
def test_get_envvar_tracer(self):
- return self.do_test_get_envvar("TRACERSOURCE")
+ return self.do_test_get_envvar("TRACERPROVIDER")
def test_get_envvar_default(self):
return self.do_test_get_envvar("DEFAULT")
diff --git a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py
similarity index 60%
rename from opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py
rename to opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py
index 8f283ef8819..6ee4a957d29 100644
--- a/opentelemetry-api/tests/context/propagation/test_tracecontexthttptextformat.py
+++ b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py
@@ -14,10 +14,13 @@
import typing
import unittest
-from unittest.mock import Mock
from opentelemetry import trace
-from opentelemetry.context.propagation import tracecontexthttptextformat
+from opentelemetry.trace.propagation import (
+ get_span_from_context,
+ set_span_in_context,
+ tracecontexthttptextformat,
+)
FORMAT = tracecontexthttptextformat.TraceContextHTTPTextFormat()
@@ -43,8 +46,8 @@ 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_context = FORMAT.extract(get_as_list, output)
- self.assertTrue(isinstance(span_context, trace.SpanContext))
+ span = get_span_from_context(FORMAT.extract(get_as_list, output))
+ self.assertIsInstance(span.get_context(), trace.SpanContext)
def test_headers_with_tracestate(self):
"""When there is a traceparent and tracestate header, data from
@@ -55,23 +58,25 @@ def test_headers_with_tracestate(self):
span_id=format(self.SPAN_ID, "016x"),
)
tracestate_value = "foo=1,bar=2,baz=3"
- span_context = FORMAT.extract(
- get_as_list,
- {
- "traceparent": [traceparent_value],
- "tracestate": [tracestate_value],
- },
- )
+ span_context = get_span_from_context(
+ FORMAT.extract(
+ get_as_list,
+ {
+ "traceparent": [traceparent_value],
+ "tracestate": [tracestate_value],
+ },
+ )
+ ).get_context()
self.assertEqual(span_context.trace_id, self.TRACE_ID)
self.assertEqual(span_context.span_id, self.SPAN_ID)
self.assertEqual(
span_context.trace_state, {"foo": "1", "bar": "2", "baz": "3"}
)
-
- mock_span = Mock()
- mock_span.configure_mock(**{"get_context.return_value": span_context})
output = {} # type:typing.Dict[str, str]
- FORMAT.inject(mock_span, dict.__setitem__, output)
+ span = trace.DefaultSpan(span_context)
+
+ ctx = 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"]:
self.assertIn(pair, output["tracestate"])
@@ -96,16 +101,18 @@ 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_context = FORMAT.extract(
- get_as_list,
- {
- "traceparent": [
- "00-00000000000000000000000000000000-1234567890123456-00"
- ],
- "tracestate": ["foo=1,bar=2,foo=3"],
- },
+ span = get_span_from_context(
+ FORMAT.extract(
+ get_as_list,
+ {
+ "traceparent": [
+ "00-00000000000000000000000000000000-1234567890123456-00"
+ ],
+ "tracestate": ["foo=1,bar=2,foo=3"],
+ },
+ )
)
- self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT)
+ self.assertEqual(span.get_context(), trace.INVALID_SPAN_CONTEXT)
def test_invalid_parent_id(self):
"""If the parent id is invalid, we must ignore the full traceparent
@@ -125,16 +132,18 @@ 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_context = FORMAT.extract(
- get_as_list,
- {
- "traceparent": [
- "00-00000000000000000000000000000000-0000000000000000-00"
- ],
- "tracestate": ["foo=1,bar=2,foo=3"],
- },
+ span = get_span_from_context(
+ FORMAT.extract(
+ get_as_list,
+ {
+ "traceparent": [
+ "00-00000000000000000000000000000000-0000000000000000-00"
+ ],
+ "tracestate": ["foo=1,bar=2,foo=3"],
+ },
+ )
)
- self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT)
+ self.assertEqual(span.get_context(), trace.INVALID_SPAN_CONTEXT)
def test_no_send_empty_tracestate(self):
"""If the tracestate is empty, do not set the header.
@@ -145,15 +154,11 @@ def test_no_send_empty_tracestate(self):
empty tracestate headers but SHOULD avoid sending them.
"""
output = {} # type:typing.Dict[str, str]
- mock_span = Mock()
- mock_span.configure_mock(
- **{
- "get_context.return_value": trace.SpanContext(
- self.TRACE_ID, self.SPAN_ID
- )
- }
+ span = trace.DefaultSpan(
+ trace.SpanContext(self.TRACE_ID, self.SPAN_ID)
)
- FORMAT.inject(mock_span, dict.__setitem__, output)
+ ctx = set_span_in_context(span)
+ FORMAT.inject(dict.__setitem__, output, ctx)
self.assertTrue("traceparent" in output)
self.assertFalse("tracestate" in output)
@@ -165,48 +170,55 @@ def test_format_not_supported(self):
If the version cannot be parsed, return an invalid trace header.
"""
- span_context = FORMAT.extract(
- get_as_list,
- {
- "traceparent": [
- "00-12345678901234567890123456789012-"
- "1234567890123456-00-residue"
- ],
- "tracestate": ["foo=1,bar=2,foo=3"],
- },
+ span = get_span_from_context(
+ FORMAT.extract(
+ get_as_list,
+ {
+ "traceparent": [
+ "00-12345678901234567890123456789012-"
+ "1234567890123456-00-residue"
+ ],
+ "tracestate": ["foo=1,bar=2,foo=3"],
+ },
+ )
)
- self.assertEqual(span_context, trace.INVALID_SPAN_CONTEXT)
+ self.assertEqual(span.get_context(), trace.INVALID_SPAN_CONTEXT)
def test_propagate_invalid_context(self):
"""Do not propagate invalid trace context."""
output = {} # type:typing.Dict[str, str]
- FORMAT.inject(trace.INVALID_SPAN, dict.__setitem__, output)
+ ctx = 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_context = FORMAT.extract(
- get_as_list,
- {
- "traceparent": [
- "00-12345678901234567890123456789012-1234567890123456-00"
- ],
- "tracestate": ["foo=1", ""],
- },
+ span = get_span_from_context(
+ FORMAT.extract(
+ get_as_list,
+ {
+ "traceparent": [
+ "00-12345678901234567890123456789012-1234567890123456-00"
+ ],
+ "tracestate": ["foo=1", ""],
+ },
+ )
)
- self.assertEqual(span_context.trace_state["foo"], "1")
+ self.assertEqual(span.get_context().trace_state["foo"], "1")
def test_tracestate_header_with_trailing_comma(self):
"""Do not propagate invalid trace context.
"""
- span_context = FORMAT.extract(
- get_as_list,
- {
- "traceparent": [
- "00-12345678901234567890123456789012-1234567890123456-00"
- ],
- "tracestate": ["foo=1,"],
- },
+ span = get_span_from_context(
+ FORMAT.extract(
+ get_as_list,
+ {
+ "traceparent": [
+ "00-12345678901234567890123456789012-1234567890123456-00"
+ ],
+ "tracestate": ["foo=1,"],
+ },
+ )
)
- self.assertEqual(span_context.trace_state["foo"], "1")
+ self.assertEqual(span.get_context().trace_state["foo"], "1")
diff --git a/opentelemetry-api/tests/trace/test_globals.py b/opentelemetry-api/tests/trace/test_globals.py
index 2ad74fb2ab3..7c4d8e3549b 100644
--- a/opentelemetry-api/tests/trace/test_globals.py
+++ b/opentelemetry-api/tests/trace/test_globals.py
@@ -8,15 +8,14 @@ class TestGlobals(unittest.TestCase):
def setUp(self):
importlib.reload(trace)
- # this class has to be declared after the importlib
- # reload, or else it will inherit from an old
- # TracerSource, rather than the new TraceSource ABC.
- # created from reload.
+ # This class has to be declared after the importlib reload, or else it
+ # will inherit from an old TracerProvider, rather than the new
+ # TracerProvider ABC created from reload.
static_tracer = trace.DefaultTracer()
- class DummyTracerSource(trace.TracerSource):
- """TraceSource used for testing"""
+ class DummyTracerProvider(trace.TracerProvider):
+ """TracerProvider used for testing"""
def get_tracer(
self,
@@ -26,8 +25,8 @@ def get_tracer(
# pylint:disable=no-self-use,unused-argument
return static_tracer
- trace.set_preferred_tracer_source_implementation(
- lambda _: DummyTracerSource()
+ trace.set_preferred_tracer_provider_implementation(
+ lambda _: DummyTracerProvider()
)
@staticmethod
@@ -35,7 +34,7 @@ def tearDown() -> None:
importlib.reload(trace)
def test_get_tracer(self):
- """trace.get_tracer should proxy to the global tracer source."""
+ """trace.get_tracer should proxy to the global tracer provider."""
from_global_api = trace.get_tracer("foo")
- from_tracer_api = trace.tracer_source().get_tracer("foo")
+ from_tracer_api = trace.tracer_provider().get_tracer("foo")
self.assertIs(from_global_api, from_tracer_api)
diff --git a/opentelemetry-api/tests/trace/test_sampling.py b/opentelemetry-api/tests/trace/test_sampling.py
index f04aecef45b..0a3d8195285 100644
--- a/opentelemetry-api/tests/trace/test_sampling.py
+++ b/opentelemetry-api/tests/trace/test_sampling.py
@@ -18,16 +18,14 @@
from opentelemetry import trace
from opentelemetry.trace import sampling
-TO_DEFAULT = trace.TraceOptions(trace.TraceOptions.DEFAULT)
-TO_SAMPLED = trace.TraceOptions(trace.TraceOptions.SAMPLED)
+TO_DEFAULT = trace.TraceFlags(trace.TraceFlags.DEFAULT)
+TO_SAMPLED = trace.TraceFlags(trace.TraceFlags.SAMPLED)
class TestSampler(unittest.TestCase):
def test_always_on(self):
no_record_always_on = sampling.ALWAYS_ON.should_sample(
- trace.SpanContext(
- 0xDEADBEEF, 0xDEADBEF0, trace_options=TO_DEFAULT
- ),
+ trace.SpanContext(0xDEADBEEF, 0xDEADBEF0, trace_flags=TO_DEFAULT),
0xDEADBEF1,
0xDEADBEF2,
"unsampled parent, sampling on",
@@ -36,9 +34,7 @@ def test_always_on(self):
self.assertEqual(no_record_always_on.attributes, {})
sampled_always_on = sampling.ALWAYS_ON.should_sample(
- trace.SpanContext(
- 0xDEADBEEF, 0xDEADBEF0, trace_options=TO_SAMPLED
- ),
+ trace.SpanContext(0xDEADBEEF, 0xDEADBEF0, trace_flags=TO_SAMPLED),
0xDEADBEF1,
0xDEADBEF2,
"sampled parent, sampling on",
@@ -48,9 +44,7 @@ def test_always_on(self):
def test_always_off(self):
no_record_always_off = sampling.ALWAYS_OFF.should_sample(
- trace.SpanContext(
- 0xDEADBEEF, 0xDEADBEF0, trace_options=TO_DEFAULT
- ),
+ trace.SpanContext(0xDEADBEEF, 0xDEADBEF0, trace_flags=TO_DEFAULT),
0xDEADBEF1,
0xDEADBEF2,
"unsampled parent, sampling off",
@@ -59,9 +53,7 @@ def test_always_off(self):
self.assertEqual(no_record_always_off.attributes, {})
sampled_always_on = sampling.ALWAYS_OFF.should_sample(
- trace.SpanContext(
- 0xDEADBEEF, 0xDEADBEF0, trace_options=TO_SAMPLED
- ),
+ trace.SpanContext(0xDEADBEEF, 0xDEADBEF0, trace_flags=TO_SAMPLED),
0xDEADBEF1,
0xDEADBEF2,
"sampled parent, sampling off",
@@ -71,9 +63,7 @@ def test_always_off(self):
def test_default_on(self):
no_record_default_on = sampling.DEFAULT_ON.should_sample(
- trace.SpanContext(
- 0xDEADBEEF, 0xDEADBEF0, trace_options=TO_DEFAULT
- ),
+ trace.SpanContext(0xDEADBEEF, 0xDEADBEF0, trace_flags=TO_DEFAULT),
0xDEADBEF1,
0xDEADBEF2,
"unsampled parent, sampling on",
@@ -82,9 +72,7 @@ def test_default_on(self):
self.assertEqual(no_record_default_on.attributes, {})
sampled_default_on = sampling.DEFAULT_ON.should_sample(
- trace.SpanContext(
- 0xDEADBEEF, 0xDEADBEF0, trace_options=TO_SAMPLED
- ),
+ trace.SpanContext(0xDEADBEEF, 0xDEADBEF0, trace_flags=TO_SAMPLED),
0xDEADBEF1,
0xDEADBEF2,
"sampled parent, sampling on",
@@ -94,9 +82,7 @@ def test_default_on(self):
def test_default_off(self):
no_record_default_off = sampling.DEFAULT_OFF.should_sample(
- trace.SpanContext(
- 0xDEADBEEF, 0xDEADBEF0, trace_options=TO_DEFAULT
- ),
+ trace.SpanContext(0xDEADBEEF, 0xDEADBEF0, trace_flags=TO_DEFAULT),
0xDEADBEF1,
0xDEADBEF2,
"unsampled parent, sampling off",
@@ -105,9 +91,7 @@ def test_default_off(self):
self.assertEqual(no_record_default_off.attributes, {})
sampled_default_off = sampling.DEFAULT_OFF.should_sample(
- trace.SpanContext(
- 0xDEADBEEF, 0xDEADBEF0, trace_options=TO_SAMPLED
- ),
+ trace.SpanContext(0xDEADBEEF, 0xDEADBEF0, trace_flags=TO_SAMPLED),
0xDEADBEF1,
0xDEADBEF2,
"sampled parent, sampling off",
@@ -136,7 +120,7 @@ def test_probability_sampler(self):
self.assertFalse(
sampler.should_sample(
trace.SpanContext(
- 0xDEADBEF0, 0xDEADBEF1, trace_options=TO_DEFAULT
+ 0xDEADBEF0, 0xDEADBEF1, trace_flags=TO_DEFAULT
),
0x7FFFFFFFFFFFFFFF,
0xDEADBEEF,
@@ -146,7 +130,7 @@ def test_probability_sampler(self):
self.assertTrue(
sampler.should_sample(
trace.SpanContext(
- 0xDEADBEF0, 0xDEADBEF1, trace_options=TO_SAMPLED
+ 0xDEADBEF0, 0xDEADBEF1, trace_flags=TO_SAMPLED
),
0x8000000000000000,
0xDEADBEEF,
diff --git a/opentelemetry-sdk/setup.py b/opentelemetry-sdk/setup.py
index cbfb0f075d4..50fe925605c 100644
--- a/opentelemetry-sdk/setup.py
+++ b/opentelemetry-sdk/setup.py
@@ -44,7 +44,7 @@
include_package_data=True,
long_description=open("README.rst").read(),
long_description_content_type="text/x-rst",
- install_requires=["opentelemetry-api==0.4.dev0"],
+ install_requires=["opentelemetry-api==0.5.dev0"],
extras_require={},
license="Apache-2.0",
package_dir={"": "src"},
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py
index 4c9214dbccb..3e03c9aa020 100644
--- a/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/context/propagation/b3_format.py
@@ -15,7 +15,17 @@
import typing
import opentelemetry.trace as trace
-from opentelemetry.context.propagation.httptextformat import HTTPTextFormat
+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,
+ HTTPTextFormatT,
+ Setter,
+)
class B3Format(HTTPTextFormat):
@@ -32,15 +42,19 @@ class B3Format(HTTPTextFormat):
FLAGS_KEY = "x-b3-flags"
_SAMPLE_PROPAGATE_VALUES = set(["1", "True", "true", "d"])
- @classmethod
- def extract(cls, get_from_carrier, carrier):
+ def extract(
+ self,
+ get_from_carrier: Getter[HTTPTextFormatT],
+ carrier: HTTPTextFormatT,
+ context: typing.Optional[Context] = None,
+ ) -> Context:
trace_id = format_trace_id(trace.INVALID_TRACE_ID)
span_id = format_span_id(trace.INVALID_SPAN_ID)
sampled = "0"
flags = None
single_header = _extract_first_element(
- get_from_carrier(carrier, cls.SINGLE_HEADER_KEY)
+ get_from_carrier(carrier, self.SINGLE_HEADER_KEY)
)
if single_header:
# The b3 spec calls for the sampling state to be
@@ -58,29 +72,29 @@ def extract(cls, get_from_carrier, carrier):
elif len(fields) == 4:
trace_id, span_id, sampled, _ = fields
else:
- return trace.INVALID_SPAN_CONTEXT
+ return set_span_in_context(trace.INVALID_SPAN)
else:
trace_id = (
_extract_first_element(
- get_from_carrier(carrier, cls.TRACE_ID_KEY)
+ get_from_carrier(carrier, self.TRACE_ID_KEY)
)
or trace_id
)
span_id = (
_extract_first_element(
- get_from_carrier(carrier, cls.SPAN_ID_KEY)
+ get_from_carrier(carrier, self.SPAN_ID_KEY)
)
or span_id
)
sampled = (
_extract_first_element(
- get_from_carrier(carrier, cls.SAMPLED_KEY)
+ get_from_carrier(carrier, self.SAMPLED_KEY)
)
or sampled
)
flags = (
_extract_first_element(
- get_from_carrier(carrier, cls.FLAGS_KEY)
+ get_from_carrier(carrier, self.FLAGS_KEY)
)
or flags
)
@@ -90,34 +104,41 @@ def extract(cls, get_from_carrier, carrier):
# flag values set. Since the setting of at least one implies
# the desire for some form of sampling, propagate if either
# header is set to allow.
- if sampled in cls._SAMPLE_PROPAGATE_VALUES or flags == "1":
- options |= trace.TraceOptions.SAMPLED
- return trace.SpanContext(
- # trace an span ids are encoded in hex, so must be converted
- trace_id=int(trace_id, 16),
- span_id=int(span_id, 16),
- trace_options=trace.TraceOptions(options),
- trace_state=trace.TraceState(),
+ if sampled in self._SAMPLE_PROPAGATE_VALUES or flags == "1":
+ options |= trace.TraceFlags.SAMPLED
+ return set_span_in_context(
+ trace.DefaultSpan(
+ trace.SpanContext(
+ # trace an span ids are encoded in hex, so must be converted
+ trace_id=int(trace_id, 16),
+ span_id=int(span_id, 16),
+ trace_flags=trace.TraceFlags(options),
+ trace_state=trace.TraceState(),
+ )
+ )
)
- @classmethod
- def inject(cls, span, set_in_carrier, carrier):
- sampled = (
- trace.TraceOptions.SAMPLED & span.context.trace_options
- ) != 0
+ def inject(
+ self,
+ set_in_carrier: Setter[HTTPTextFormatT],
+ carrier: HTTPTextFormatT,
+ context: typing.Optional[Context] = None,
+ ) -> None:
+ span = get_span_from_context(context=context)
+ sampled = (trace.TraceFlags.SAMPLED & span.context.trace_flags) != 0
set_in_carrier(
- carrier, cls.TRACE_ID_KEY, format_trace_id(span.context.trace_id)
+ carrier, self.TRACE_ID_KEY, format_trace_id(span.context.trace_id),
)
set_in_carrier(
- carrier, cls.SPAN_ID_KEY, format_span_id(span.context.span_id)
+ carrier, self.SPAN_ID_KEY, format_span_id(span.context.span_id)
)
if span.parent is not None:
set_in_carrier(
carrier,
- cls.PARENT_SPAN_ID_KEY,
+ self.PARENT_SPAN_ID_KEY,
format_span_id(span.parent.context.span_id),
)
- set_in_carrier(carrier, cls.SAMPLED_KEY, "1" if sampled else "0")
+ set_in_carrier(carrier, self.SAMPLED_KEY, "1" if sampled else "0")
def format_trace_id(trace_id: int) -> str:
@@ -130,10 +151,9 @@ def format_span_id(span_id: int) -> str:
return format(span_id, "016x")
-_T = typing.TypeVar("_T")
-
-
-def _extract_first_element(items: typing.Iterable[_T]) -> typing.Optional[_T]:
+def _extract_first_element(
+ items: typing.Iterable[HTTPTextFormatT],
+) -> typing.Optional[HTTPTextFormatT]:
if items is None:
return None
return next(iter(items), None)
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py
index 4c9231582c8..fc0fe6ae521 100644
--- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2019, OpenTelemetry Authors
+# Copyright 2020, OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
from opentelemetry import metrics as metrics_api
from opentelemetry.sdk.metrics.export.aggregate import Aggregator
from opentelemetry.sdk.metrics.export.batcher import Batcher, UngroupedBatcher
+from opentelemetry.sdk.util.instrumentation import InstrumentationInfo
from opentelemetry.util import time_ns
logger = logging.getLogger(__name__)
@@ -99,13 +100,6 @@ def add(self, value: metrics_api.ValueT) -> None:
self.update(value)
-class GaugeHandle(metrics_api.GaugeHandle, BaseHandle):
- def set(self, value: metrics_api.ValueT) -> None:
- """See `opentelemetry.metrics.GaugeHandle.set`."""
- if self._validate_update(value):
- self.update(value)
-
-
class MeasureHandle(metrics_api.MeasureHandle, BaseHandle):
def record(self, value: metrics_api.ValueT) -> None:
"""See `opentelemetry.metrics.MeasureHandle.record`."""
@@ -157,7 +151,7 @@ def get_handle(self, label_set: LabelSet) -> BaseHandle:
return handle
def __repr__(self):
- return '{}(name="{}", description={})'.format(
+ return '{}(name="{}", description="{}")'.format(
type(self).__name__, self.name, self.description
)
@@ -197,14 +191,24 @@ def add(self, value: metrics_api.ValueT, label_set: LabelSet) -> None:
UPDATE_FUNCTION = add
-class Gauge(Metric, metrics_api.Gauge):
- """See `opentelemetry.metrics.Gauge`.
- """
+class Measure(Metric, metrics_api.Measure):
+ """See `opentelemetry.metrics.Measure`."""
- HANDLE_TYPE = GaugeHandle
+ HANDLE_TYPE = MeasureHandle
+
+ def record(self, value: metrics_api.ValueT, label_set: LabelSet) -> None:
+ """See `opentelemetry.metrics.Measure.record`."""
+ self.get_handle(label_set).record(value)
+
+ UPDATE_FUNCTION = record
+
+
+class Observer(metrics_api.Observer):
+ """See `opentelemetry.metrics.Observer`."""
def __init__(
self,
+ callback: metrics_api.ObserverCallbackT,
name: str,
description: str,
unit: str,
@@ -213,40 +217,59 @@ def __init__(
label_keys: Sequence[str] = (),
enabled: bool = True,
):
- super().__init__(
- name,
- description,
- unit,
- value_type,
- meter,
- label_keys=label_keys,
- enabled=enabled,
- )
-
- def set(self, value: metrics_api.ValueT, label_set: LabelSet) -> None:
- """See `opentelemetry.metrics.Gauge.set`."""
- self.get_handle(label_set).set(value)
-
- UPDATE_FUNCTION = set
-
+ self.callback = callback
+ self.name = name
+ self.description = description
+ self.unit = unit
+ self.value_type = value_type
+ self.meter = meter
+ self.label_keys = label_keys
+ self.enabled = enabled
-class Measure(Metric, metrics_api.Measure):
- """See `opentelemetry.metrics.Measure`."""
+ self.aggregators = {}
- HANDLE_TYPE = MeasureHandle
+ def observe(self, value: metrics_api.ValueT, label_set: LabelSet) -> None:
+ if not self.enabled:
+ return
+ if not isinstance(value, self.value_type):
+ logger.warning(
+ "Invalid value passed for %s.", self.value_type.__name__
+ )
+ return
- def record(self, value: metrics_api.ValueT, label_set: LabelSet) -> None:
- """See `opentelemetry.metrics.Measure.record`."""
- self.get_handle(label_set).record(value)
+ if label_set not in self.aggregators:
+ # TODO: how to cleanup aggregators?
+ self.aggregators[label_set] = self.meter.batcher.aggregator_for(
+ self.__class__
+ )
+ aggregator = self.aggregators[label_set]
+ aggregator.update(value)
+
+ def run(self) -> bool:
+ try:
+ self.callback(self)
+ # pylint: disable=broad-except
+ except Exception as exc:
+ logger.warning(
+ "Exception while executing observer callback: %s.", exc
+ )
+ return False
+ return True
- UPDATE_FUNCTION = record
+ def __repr__(self):
+ return '{}(name="{}", description="{}")'.format(
+ type(self).__name__, self.name, self.description
+ )
class Record:
"""Container class used for processing in the `Batcher`"""
def __init__(
- self, metric: Metric, label_set: LabelSet, aggregator: Aggregator
+ self,
+ metric: metrics_api.MetricT,
+ label_set: LabelSet,
+ aggregator: Aggregator,
):
self.metric = metric
self.label_set = label_set
@@ -261,12 +284,17 @@ class Meter(metrics_api.Meter):
"""See `opentelemetry.metrics.Meter`.
Args:
- batcher: The `Batcher` used for this meter.
+ instrumentation_info: The `InstrumentationInfo` for this meter.
+ stateful: Indicates whether the meter is stateful.
"""
- def __init__(self, batcher: Batcher = UngroupedBatcher(True)):
- self.batcher = batcher
+ def __init__(
+ self, instrumentation_info: "InstrumentationInfo", stateful: bool,
+ ):
+ self.instrumentation_info = instrumentation_info
self.metrics = set()
+ self.observers = set()
+ self.batcher = UngroupedBatcher(stateful)
def collect(self) -> None:
"""Collects all the metrics created with this `Meter` for export.
@@ -275,6 +303,11 @@ def collect(self) -> None:
each aggregator belonging to the metrics that were created with this
meter instance.
"""
+
+ self._collect_metrics()
+ self._collect_observers()
+
+ def _collect_metrics(self) -> None:
for metric in self.metrics:
if metric.enabled:
for label_set, handle in metric.handles.items():
@@ -284,6 +317,19 @@ def collect(self) -> None:
# Applies different batching logic based on type of batcher
self.batcher.process(record)
+ def _collect_observers(self) -> None:
+ for observer in self.observers:
+ if not observer.enabled:
+ continue
+
+ # TODO: capture timestamp?
+ if not observer.run():
+ continue
+
+ for label_set, aggregator in observer.aggregators.items():
+ record = Record(observer, label_set, aggregator)
+ self.batcher.process(record)
+
def record_batch(
self,
label_set: LabelSet,
@@ -317,6 +363,29 @@ def create_metric(
self.metrics.add(metric)
return metric
+ def register_observer(
+ self,
+ callback: metrics_api.ObserverCallbackT,
+ name: str,
+ description: str,
+ unit: str,
+ value_type: Type[metrics_api.ValueT],
+ label_keys: Sequence[str] = (),
+ enabled: bool = True,
+ ) -> metrics_api.Observer:
+ ob = Observer(
+ callback,
+ name,
+ description,
+ unit,
+ value_type,
+ self,
+ label_keys,
+ enabled,
+ )
+ self.observers.add(ob)
+ return ob
+
def get_label_set(self, labels: Dict[str, str]):
"""See `opentelemetry.metrics.Meter.create_metric`.
@@ -328,3 +397,20 @@ def get_label_set(self, labels: Dict[str, str]):
if len(labels) == 0:
return EMPTY_LABEL_SET
return LabelSet(labels=labels)
+
+
+class MeterProvider(metrics_api.MeterProvider):
+ def get_meter(
+ self,
+ instrumenting_module_name: str,
+ stateful=True,
+ instrumenting_library_version: str = "",
+ ) -> "metrics_api.Meter":
+ if not instrumenting_module_name: # Reject empty strings too.
+ raise ValueError("get_meter called with missing module name.")
+ return Meter(
+ InstrumentationInfo(
+ instrumenting_module_name, instrumenting_library_version
+ ),
+ stateful=stateful,
+ )
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/aggregate.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/aggregate.py
index 5c55ba038ac..5b730cc8040 100644
--- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/aggregate.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/aggregate.py
@@ -13,6 +13,7 @@
# limitations under the License.
import abc
+import threading
from collections import namedtuple
@@ -47,62 +48,95 @@ def __init__(self):
super().__init__()
self.current = 0
self.checkpoint = 0
+ self._lock = threading.Lock()
def update(self, value):
- self.current += value
+ with self._lock:
+ self.current += value
def take_checkpoint(self):
- self.checkpoint = self.current
- self.current = 0
+ with self._lock:
+ self.checkpoint = self.current
+ self.current = 0
def merge(self, other):
- self.checkpoint += other.checkpoint
+ with self._lock:
+ self.checkpoint += other.checkpoint
class MinMaxSumCountAggregator(Aggregator):
"""Agregator for Measure metrics that keeps min, max, sum and count."""
_TYPE = namedtuple("minmaxsumcount", "min max sum count")
+ _EMPTY = _TYPE(None, None, None, 0)
@classmethod
- def _min(cls, val1, val2):
- if val1 is None and val2 is None:
- return None
- return min(val1 or val2, val2 or val1)
+ def _merge_checkpoint(cls, val1, val2):
+ if val1 is cls._EMPTY:
+ return val2
+ if val2 is cls._EMPTY:
+ return val1
+ return cls._TYPE(
+ min(val1.min, val2.min),
+ max(val1.max, val2.max),
+ val1.sum + val2.sum,
+ val1.count + val2.count,
+ )
- @classmethod
- def _max(cls, val1, val2):
- if val1 is None and val2 is None:
- return None
- return max(val1 or val2, val2 or val1)
+ def __init__(self):
+ super().__init__()
+ self.current = self._EMPTY
+ self.checkpoint = self._EMPTY
+ self._lock = threading.Lock()
+
+ def update(self, value):
+ with self._lock:
+ if self.current is self._EMPTY:
+ self.current = self._TYPE(value, value, value, 1)
+ else:
+ self.current = self._TYPE(
+ min(self.current.min, value),
+ max(self.current.max, value),
+ self.current.sum + value,
+ self.current.count + 1,
+ )
+
+ def take_checkpoint(self):
+ with self._lock:
+ self.checkpoint = self.current
+ self.current = self._EMPTY
+
+ def merge(self, other):
+ with self._lock:
+ self.checkpoint = self._merge_checkpoint(
+ self.checkpoint, other.checkpoint
+ )
- @classmethod
- def _sum(cls, val1, val2):
- if val1 is None and val2 is None:
- return None
- return (val1 or 0) + (val2 or 0)
+
+class ObserverAggregator(Aggregator):
+ """Same as MinMaxSumCount but also with last value."""
+
+ _TYPE = namedtuple("minmaxsumcountlast", "min max sum count last")
def __init__(self):
super().__init__()
- self.current = self._TYPE(None, None, None, 0)
- self.checkpoint = self._TYPE(None, None, None, 0)
+ self.mmsc = MinMaxSumCountAggregator()
+ self.current = None
+ self.checkpoint = self._TYPE(None, None, None, 0, None)
def update(self, value):
- self.current = self._TYPE(
- self._min(self.current.min, value),
- self._max(self.current.max, value),
- self._sum(self.current.sum, value),
- self.current.count + 1,
- )
+ self.mmsc.update(value)
+ self.current = value
def take_checkpoint(self):
- self.checkpoint = self.current
- self.current = self._TYPE(None, None, None, 0)
+ self.mmsc.take_checkpoint()
+ self.checkpoint = self._TYPE(*(self.mmsc.checkpoint + (self.current,)))
def merge(self, other):
+ self.mmsc.merge(other.mmsc)
self.checkpoint = self._TYPE(
- self._min(self.checkpoint.min, other.checkpoint.min),
- self._max(self.checkpoint.max, other.checkpoint.max),
- self._sum(self.checkpoint.sum, other.checkpoint.sum),
- self.checkpoint.count + other.checkpoint.count,
+ *(
+ self.mmsc.checkpoint
+ + (other.checkpoint.last or self.checkpoint.last,)
+ )
)
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/batcher.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/batcher.py
index 86ddc3fcc16..f4418c61399 100644
--- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/batcher.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/export/batcher.py
@@ -15,12 +15,13 @@
import abc
from typing import Sequence, Type
-from opentelemetry.metrics import Counter, Measure, MetricT
+from opentelemetry.metrics import Counter, Measure, MetricT, Observer
from opentelemetry.sdk.metrics.export import MetricRecord
from opentelemetry.sdk.metrics.export.aggregate import (
Aggregator,
CounterAggregator,
MinMaxSumCountAggregator,
+ ObserverAggregator,
)
@@ -50,6 +51,8 @@ def aggregator_for(self, metric_type: Type[MetricT]) -> Aggregator:
return CounterAggregator()
if issubclass(metric_type, Measure):
return MinMaxSumCountAggregator()
+ if issubclass(metric_type, Observer):
+ return ObserverAggregator()
# TODO: Add other aggregators
return CounterAggregator()
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
index 9a285a458da..648e06cf8c7 100644
--- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
@@ -18,6 +18,7 @@
import random
import threading
from contextlib import contextmanager
+from contextvars import ContextVar
from numbers import Number
from types import TracebackType
from typing import Iterator, Optional, Sequence, Tuple, Type
@@ -26,11 +27,14 @@
from opentelemetry import trace as trace_api
from opentelemetry.sdk import util
from opentelemetry.sdk.util import BoundedDict, BoundedList
+from opentelemetry.sdk.util.instrumentation import InstrumentationInfo
from opentelemetry.trace import SpanContext, sampling
from opentelemetry.trace.propagation import SPAN_KEY
from opentelemetry.trace.status import Status, StatusCanonicalCode
from opentelemetry.util import time_ns, types
+CURRENT_SPANS: ContextVar[dict] = ContextVar("spans", default={})
+
logger = logging.getLogger(__name__)
MAX_NUM_ATTRIBUTES = 32
@@ -43,7 +47,7 @@ class SpanProcessor:
invocations.
Span processors can be registered directly using
- :func:`TracerSource.add_span_processor` and they are invoked
+ :func:`TracerProvider.add_span_processor` and they are invoked
in the same order as they were registered.
"""
@@ -142,7 +146,7 @@ class Span(trace_api.Span):
def __init__(
self,
name: str,
- context: trace_api.SpanContext,
+ context: trace_api.SpanContext = trace_api.INVALID_SPAN_CONTEXT,
parent: trace_api.ParentSpan = None,
sampler: Optional[sampling.Sampler] = None,
trace_config: None = None, # TODO
@@ -152,7 +156,7 @@ def __init__(
links: Sequence[trace_api.Link] = (),
kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
span_processor: SpanProcessor = SpanProcessor(),
- instrumentation_info: "InstrumentationInfo" = None,
+ instrumentation_info: InstrumentationInfo = None,
set_status_on_exception: bool = True,
) -> None:
@@ -309,8 +313,6 @@ def end(self, end_time: Optional[int] = None) -> None:
with self._lock:
if not self.is_recording_events():
return
- if self.start_time is None:
- raise RuntimeError("Calling end() on a not started span.")
has_ended = self.end_time is not None
if not has_ended:
if self.status is None:
@@ -385,46 +387,6 @@ def generate_trace_id() -> int:
return random.getrandbits(128)
-class InstrumentationInfo:
- """Immutable information about an instrumentation library module.
-
- See `TracerSource.get_tracer` for the meaning of the properties.
- """
-
- __slots__ = ("_name", "_version")
-
- def __init__(self, name: str, version: str):
- self._name = name
- self._version = version
-
- def __repr__(self):
- return "{}({}, {})".format(
- type(self).__name__, self._name, self._version
- )
-
- def __hash__(self):
- return hash((self._name, self._version))
-
- def __eq__(self, value):
- return type(value) is type(self) and (self._name, self._version) == (
- value._name,
- value._version,
- )
-
- def __lt__(self, value):
- if type(value) is not type(self):
- return NotImplemented
- return (self._name, self._version) < (value._name, value._version)
-
- @property
- def version(self) -> str:
- return self._version
-
- @property
- def name(self) -> str:
- return self._name
-
-
class Tracer(trace_api.Tracer):
"""See `opentelemetry.trace.Tracer`.
@@ -435,14 +397,16 @@ class Tracer(trace_api.Tracer):
"""
def __init__(
- self, source: "TracerSource", instrumentation_info: InstrumentationInfo
+ self,
+ source: "TracerProvider",
+ instrumentation_info: InstrumentationInfo,
) -> None:
self.source = source
self.instrumentation_info = instrumentation_info
def get_current_span(self):
"""See `opentelemetry.trace.Tracer.get_current_span`."""
- return self.source.get_current_span()
+ return self.source.get_current_span(self.instrumentation_info.name)
def start_as_current_span(
self,
@@ -484,15 +448,15 @@ def start_span( # pylint: disable=too-many-locals
if parent_context is None or not parent_context.is_valid():
parent = parent_context = None
trace_id = generate_trace_id()
- trace_options = None
+ trace_flags = None
trace_state = None
else:
trace_id = parent_context.trace_id
- trace_options = parent_context.trace_options
+ trace_flags = parent_context.trace_flags
trace_state = parent_context.trace_state
context = trace_api.SpanContext(
- trace_id, generate_span_id(), trace_options, trace_state
+ trace_id, generate_span_id(), trace_flags, trace_state
)
# The sampler decides whether to create a real or no-op span at the
@@ -510,8 +474,8 @@ def start_span( # pylint: disable=too-many-locals
)
if sampling_decision.sampled:
- options = context.trace_options | trace_api.TraceOptions.SAMPLED
- context.trace_options = trace_api.TraceOptions(options)
+ options = context.trace_flags | trace_api.TraceFlags.SAMPLED
+ context.trace_flags = trace_api.TraceFlags(options)
if attributes is None:
span_attributes = sampling_decision.attributes
else:
@@ -540,13 +504,13 @@ def use_span(
self, span: trace_api.Span, end_on_exit: bool = False
) -> Iterator[trace_api.Span]:
"""See `opentelemetry.trace.Tracer.use_span`."""
+ name = self.instrumentation_info.name
try:
- context_snapshot = context_api.get_current()
- context_api.set_current(context_api.set_value(SPAN_KEY, span))
+ CURRENT_SPANS.get()[f"{SPAN_KEY}.{name}"] = span
try:
yield span
finally:
- context_api.set_current(context_snapshot)
+ CURRENT_SPANS.get()[f"{SPAN_KEY}.{name}"] = None
except Exception as error: # pylint: disable=broad-except
if (
@@ -569,7 +533,7 @@ def use_span(
span.end()
-class TracerSource(trace_api.TracerSource):
+class TracerProvider(trace_api.TracerProvider):
def __init__(
self,
sampler: sampling.Sampler = trace_api.sampling.ALWAYS_ON,
@@ -597,11 +561,11 @@ def get_tracer(
)
@staticmethod
- def get_current_span() -> Span:
- return context_api.get_value(SPAN_KEY) # type: ignore
+ def get_current_span(name: str) -> Span:
+ return CURRENT_SPANS.get().get(f"{SPAN_KEY}.{name}") # type: ignore
def add_span_processor(self, span_processor: SpanProcessor) -> None:
- """Registers a new :class:`SpanProcessor` for this `TracerSource`.
+ """Registers a new :class:`SpanProcessor` for this `TracerProvider`.
The span processors are invoked in the same order they are registered.
"""
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py
index 0a1b1c8041d..e5d96eff9e9 100644
--- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py
@@ -14,12 +14,13 @@
import collections
import logging
+import os
import sys
import threading
import typing
from enum import Enum
-from opentelemetry.context import get_current, set_current, set_value
+from opentelemetry.context import attach, detach, get_current, set_value
from opentelemetry.trace import DefaultSpan
from opentelemetry.util import time_ns
@@ -75,14 +76,13 @@ def on_start(self, span: Span) -> None:
pass
def on_end(self, span: Span) -> None:
- backup_context = get_current()
- set_current(set_value("suppress_instrumentation", True))
+ token = attach(set_value("suppress_instrumentation", True))
try:
self.span_exporter.export((span,))
# pylint: disable=broad-except
except Exception:
logger.exception("Exception while exporting Span.")
- set_current(backup_context)
+ detach(token)
def shutdown(self) -> None:
self.span_exporter.shutdown()
@@ -202,8 +202,7 @@ def export(self) -> None:
else:
self.spans_list[idx] = span
idx += 1
- backup_context = get_current()
- set_current(set_value("suppress_instrumentation", True))
+ token = attach(set_value("suppress_instrumentation", True))
try:
# Ignore type b/c the Optional[None]+slicing is too "clever"
# for mypy
@@ -211,7 +210,7 @@ def export(self) -> None:
# pylint: disable=broad-except
except Exception:
logger.exception("Exception while exporting Span batch.")
- set_current(backup_context)
+ detach(token)
if notify_flush:
with self.flush_condition:
@@ -272,7 +271,8 @@ class ConsoleSpanExporter(SpanExporter):
def __init__(
self,
out: typing.IO = sys.stdout,
- formatter: typing.Callable[[Span], str] = str,
+ formatter: typing.Callable[[Span], str] = lambda span: str(span)
+ + os.linesep,
):
self.out = out
self.formatter = formatter
@@ -280,4 +280,5 @@ def __init__(
def export(self, spans: typing.Sequence[Span]) -> SpanExportResult:
for span in spans:
self.out.write(self.formatter(span))
+ self.out.flush()
return SpanExportResult.SUCCESS
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/util.py b/opentelemetry-sdk/src/opentelemetry/sdk/util/__init__.py
similarity index 99%
rename from opentelemetry-sdk/src/opentelemetry/sdk/util.py
rename to opentelemetry-sdk/src/opentelemetry/sdk/util/__init__.py
index 2265c29460b..009a0bcdd73 100644
--- a/opentelemetry-sdk/src/opentelemetry/sdk/util.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/util/__init__.py
@@ -11,7 +11,6 @@
# 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 datetime
import threading
from collections import OrderedDict, deque
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py b/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py
new file mode 100644
index 00000000000..893a6066d9f
--- /dev/null
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py
@@ -0,0 +1,55 @@
+# Copyright 2020, 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.
+
+
+class InstrumentationInfo:
+ """Immutable information about an instrumentation library module.
+
+ See `opentelemetry.trace.TracerProvider.get_tracer` or
+ `opentelemetry.metrics.MeterProvider.get_meter` for the meaning of these
+ properties.
+ """
+
+ __slots__ = ("_name", "_version")
+
+ def __init__(self, name: str, version: str):
+ self._name = name
+ self._version = version
+
+ def __repr__(self):
+ return "{}({}, {})".format(
+ type(self).__name__, self._name, self._version
+ )
+
+ def __hash__(self):
+ return hash((self._name, self._version))
+
+ def __eq__(self, value):
+ return type(value) is type(self) and (self._name, self._version) == (
+ value._name,
+ value._version,
+ )
+
+ def __lt__(self, value):
+ if type(value) is not type(self):
+ return NotImplemented
+ return (self._name, self._version) < (value._name, value._version)
+
+ @property
+ def version(self) -> str:
+ return self._version
+
+ @property
+ def name(self) -> str:
+ return self._name
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/version.py b/opentelemetry-sdk/src/opentelemetry/sdk/version.py
index 2f792fff802..d13bf967481 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.4.dev0"
+__version__ = "0.5.dev0"
diff --git a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py
index 17f7fdf7cae..0cdda1bcd0b 100644
--- a/opentelemetry-sdk/tests/context/propagation/test_b3_format.py
+++ b/opentelemetry-sdk/tests/context/propagation/test_b3_format.py
@@ -17,6 +17,10 @@
import opentelemetry.sdk.context.propagation.b3_format as b3_format
import opentelemetry.sdk.trace as trace
import opentelemetry.trace as trace_api
+from opentelemetry.trace.propagation import (
+ get_span_from_context,
+ set_span_in_context,
+)
FORMAT = b3_format.B3Format()
@@ -28,7 +32,8 @@ def get_as_list(dict_object, key):
def get_child_parent_new_carrier(old_carrier):
- parent_context = FORMAT.extract(get_as_list, old_carrier)
+ ctx = FORMAT.extract(get_as_list, old_carrier)
+ parent_context = get_span_from_context(ctx).get_context()
parent = trace.Span("parent", parent_context)
child = trace.Span(
@@ -36,14 +41,15 @@ def get_child_parent_new_carrier(old_carrier):
trace_api.SpanContext(
parent_context.trace_id,
trace.generate_span_id(),
- trace_options=parent_context.trace_options,
+ trace_flags=parent_context.trace_flags,
trace_state=parent_context.trace_state,
),
parent=parent,
)
new_carrier = {}
- FORMAT.inject(child, dict.__setitem__, new_carrier)
+ ctx = set_span_in_context(child)
+ FORMAT.inject(dict.__setitem__, new_carrier, context=ctx)
return child, parent, new_carrier
@@ -222,7 +228,8 @@ def test_invalid_single_header(self):
invalid SpanContext.
"""
carrier = {FORMAT.SINGLE_HEADER_KEY: "0-1-2-3-4-5-6-7"}
- span_context = FORMAT.extract(get_as_list, carrier)
+ ctx = FORMAT.extract(get_as_list, carrier)
+ span_context = get_span_from_context(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)
@@ -232,7 +239,9 @@ def test_missing_trace_id(self):
FORMAT.SPAN_ID_KEY: self.serialized_span_id,
FORMAT.FLAGS_KEY: "1",
}
- span_context = FORMAT.extract(get_as_list, carrier)
+
+ ctx = FORMAT.extract(get_as_list, carrier)
+ span_context = get_span_from_context(ctx).get_context()
self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID)
def test_missing_span_id(self):
@@ -241,5 +250,7 @@ def test_missing_span_id(self):
FORMAT.TRACE_ID_KEY: self.serialized_trace_id,
FORMAT.FLAGS_KEY: "1",
}
- span_context = FORMAT.extract(get_as_list, carrier)
+
+ ctx = FORMAT.extract(get_as_list, carrier)
+ span_context = get_span_from_context(ctx).get_context()
self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID)
diff --git a/opentelemetry-sdk/tests/context/test_asyncio.py b/opentelemetry-sdk/tests/context/test_asyncio.py
index e1cb90f452a..98716e06a60 100644
--- a/opentelemetry-sdk/tests/context/test_asyncio.py
+++ b/opentelemetry-sdk/tests/context/test_asyncio.py
@@ -63,50 +63,13 @@ def submit_another_task(self, name):
self.loop.create_task(self.task(name))
def setUp(self):
- self.previous_context = context.get_current()
- context.set_current(context.Context())
- self.tracer_source = trace.TracerSource()
- self.tracer = self.tracer_source.get_tracer(__name__)
+ self.token = context.attach(context.Context())
+ self.tracer_provider = trace.TracerProvider()
+ self.tracer = self.tracer_provider.get_tracer(__name__)
self.memory_exporter = InMemorySpanExporter()
span_processor = export.SimpleExportSpanProcessor(self.memory_exporter)
- self.tracer_source.add_span_processor(span_processor)
+ self.tracer_provider.add_span_processor(span_processor)
self.loop = asyncio.get_event_loop()
def tearDown(self):
- context.set_current(self.previous_context)
-
- @patch(
- "opentelemetry.context._RUNTIME_CONTEXT", ContextVarsRuntimeContext()
- )
- def test_with_asyncio(self):
- with self.tracer.start_as_current_span("asyncio_test"):
- for name in _SPAN_NAMES:
- self.submit_another_task(name)
-
- stop_loop_when(
- self.loop,
- lambda: len(self.memory_exporter.get_finished_spans()) >= 5,
- timeout=5.0,
- )
- self.loop.run_forever()
- span_list = self.memory_exporter.get_finished_spans()
- span_names_list = [span.name for span in span_list]
- expected = [
- "test_span1",
- "test_span2",
- "test_span3",
- "test_span4",
- "test_span5",
- "asyncio_test",
- ]
- self.assertCountEqual(span_names_list, expected)
- span_names_list.sort()
- expected.sort()
- self.assertListEqual(span_names_list, expected)
- expected_parent = next(
- span for span in span_list if span.name == "asyncio_test"
- )
- for span in span_list:
- if span is expected_parent:
- continue
- self.assertEqual(span.parent, expected_parent)
+ context.detach(self.token)
diff --git a/opentelemetry-sdk/tests/metrics/export/test_export.py b/opentelemetry-sdk/tests/metrics/export/test_export.py
index 5df6c6d08a0..3aab1632ec3 100644
--- a/opentelemetry-sdk/tests/metrics/export/test_export.py
+++ b/opentelemetry-sdk/tests/metrics/export/test_export.py
@@ -1,4 +1,4 @@
-# Copyright 2019, OpenTelemetry Authors
+# Copyright 2020, OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import concurrent.futures
+import random
import unittest
from unittest import mock
@@ -23,6 +25,7 @@
from opentelemetry.sdk.metrics.export.aggregate import (
CounterAggregator,
MinMaxSumCountAggregator,
+ ObserverAggregator,
)
from opentelemetry.sdk.metrics.export.batcher import UngroupedBatcher
from opentelemetry.sdk.metrics.export.controller import PushController
@@ -32,7 +35,7 @@
class TestConsoleMetricsExporter(unittest.TestCase):
# pylint: disable=no-self-use
def test_export(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
exporter = ConsoleMetricsExporter()
metric = metrics.Counter(
"available memory",
@@ -43,7 +46,7 @@ def test_export(self):
("environment",),
)
kvp = {"environment": "staging"}
- label_set = meter.get_label_set(kvp)
+ label_set = metrics.LabelSet(kvp)
aggregator = CounterAggregator()
record = MetricRecord(aggregator, label_set, metric)
result = '{}(data="{}", label_set="{}", value={})'.format(
@@ -69,7 +72,7 @@ def test_aggregator_for_counter(self):
# TODO: Add other aggregator tests
def test_checkpoint_set(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
batcher = UngroupedBatcher(True)
aggregator = CounterAggregator()
metric = metrics.Counter(
@@ -97,7 +100,7 @@ def test_checkpoint_set_empty(self):
self.assertEqual(len(records), 0)
def test_finished_collection_stateless(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
batcher = UngroupedBatcher(False)
aggregator = CounterAggregator()
metric = metrics.Counter(
@@ -117,7 +120,7 @@ def test_finished_collection_stateless(self):
self.assertEqual(len(batcher._batch_map), 0)
def test_finished_collection_stateful(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
batcher = UngroupedBatcher(True)
aggregator = CounterAggregator()
metric = metrics.Counter(
@@ -138,7 +141,7 @@ def test_finished_collection_stateful(self):
# TODO: Abstract the logic once other batchers implemented
def test_ungrouped_batcher_process_exists(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
batcher = UngroupedBatcher(True)
aggregator = CounterAggregator()
aggregator2 = CounterAggregator()
@@ -167,7 +170,7 @@ def test_ungrouped_batcher_process_exists(self):
)
def test_ungrouped_batcher_process_not_exists(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
batcher = UngroupedBatcher(True)
aggregator = CounterAggregator()
metric = metrics.Counter(
@@ -194,7 +197,7 @@ def test_ungrouped_batcher_process_not_exists(self):
)
def test_ungrouped_batcher_process_not_stateful(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
batcher = UngroupedBatcher(True)
aggregator = CounterAggregator()
metric = metrics.Counter(
@@ -222,6 +225,15 @@ def test_ungrouped_batcher_process_not_stateful(self):
class TestCounterAggregator(unittest.TestCase):
+ @staticmethod
+ def call_update(counter):
+ update_total = 0
+ for _ in range(0, 100000):
+ val = random.getrandbits(32)
+ counter.update(val)
+ update_total += val
+ return update_total
+
def test_update(self):
counter = CounterAggregator()
counter.update(1.0)
@@ -243,14 +255,57 @@ def test_merge(self):
counter.merge(counter2)
self.assertEqual(counter.checkpoint, 4.0)
+ def test_concurrent_update(self):
+ counter = CounterAggregator()
+
+ with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
+ fut1 = executor.submit(self.call_update, counter)
+ fut2 = executor.submit(self.call_update, counter)
+
+ updapte_total = fut1.result() + fut2.result()
+
+ counter.take_checkpoint()
+ self.assertEqual(updapte_total, counter.checkpoint)
+
+ def test_concurrent_update_and_checkpoint(self):
+ counter = CounterAggregator()
+ checkpoint_total = 0
+
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
+ fut = executor.submit(self.call_update, counter)
+
+ while not fut.done():
+ counter.take_checkpoint()
+ checkpoint_total += counter.checkpoint
+
+ counter.take_checkpoint()
+ checkpoint_total += counter.checkpoint
+
+ self.assertEqual(fut.result(), checkpoint_total)
+
class TestMinMaxSumCountAggregator(unittest.TestCase):
+ @staticmethod
+ def call_update(mmsc):
+ min_ = float("inf")
+ max_ = float("-inf")
+ sum_ = 0
+ count_ = 0
+ for _ in range(0, 100000):
+ val = random.getrandbits(32)
+ mmsc.update(val)
+ if val < min_:
+ min_ = val
+ if val > max_:
+ max_ = val
+ sum_ += val
+ count_ += 1
+ return MinMaxSumCountAggregator._TYPE(min_, max_, sum_, count_)
+
def test_update(self):
mmsc = MinMaxSumCountAggregator()
# test current values without any update
- self.assertEqual(
- mmsc.current, (None, None, None, 0),
- )
+ self.assertEqual(mmsc.current, MinMaxSumCountAggregator._EMPTY)
# call update with some values
values = (3, 50, 3, 97)
@@ -258,7 +313,7 @@ def test_update(self):
mmsc.update(val)
self.assertEqual(
- mmsc.current, (min(values), max(values), sum(values), len(values)),
+ mmsc.current, (min(values), max(values), sum(values), len(values))
)
def test_checkpoint(self):
@@ -266,9 +321,7 @@ def test_checkpoint(self):
# take checkpoint wihtout any update
mmsc.take_checkpoint()
- self.assertEqual(
- mmsc.checkpoint, (None, None, None, 0),
- )
+ self.assertEqual(mmsc.checkpoint, MinMaxSumCountAggregator._EMPTY)
# call update with some values
values = (3, 50, 3, 97)
@@ -281,9 +334,7 @@ def test_checkpoint(self):
(min(values), max(values), sum(values), len(values)),
)
- self.assertEqual(
- mmsc.current, (None, None, None, 0),
- )
+ self.assertEqual(mmsc.current, MinMaxSumCountAggregator._EMPTY)
def test_merge(self):
mmsc1 = MinMaxSumCountAggregator()
@@ -299,14 +350,34 @@ def test_merge(self):
self.assertEqual(
mmsc1.checkpoint,
- (
- min(checkpoint1.min, checkpoint2.min),
- max(checkpoint1.max, checkpoint2.max),
- checkpoint1.sum + checkpoint2.sum,
- checkpoint1.count + checkpoint2.count,
+ MinMaxSumCountAggregator._merge_checkpoint(
+ checkpoint1, checkpoint2
),
)
+ def test_merge_checkpoint(self):
+ func = MinMaxSumCountAggregator._merge_checkpoint
+ _type = MinMaxSumCountAggregator._TYPE
+ empty = MinMaxSumCountAggregator._EMPTY
+
+ ret = func(empty, empty)
+ self.assertEqual(ret, empty)
+
+ ret = func(empty, _type(0, 0, 0, 0))
+ self.assertEqual(ret, _type(0, 0, 0, 0))
+
+ ret = func(_type(0, 0, 0, 0), empty)
+ self.assertEqual(ret, _type(0, 0, 0, 0))
+
+ ret = func(_type(0, 0, 0, 0), _type(0, 0, 0, 0))
+ self.assertEqual(ret, _type(0, 0, 0, 0))
+
+ ret = func(_type(44, 23, 55, 86), empty)
+ self.assertEqual(ret, _type(44, 23, 55, 86))
+
+ ret = func(_type(3, 150, 101, 3), _type(1, 33, 44, 2))
+ self.assertEqual(ret, _type(1, 150, 101 + 44, 2 + 3))
+
def test_merge_with_empty(self):
mmsc1 = MinMaxSumCountAggregator()
mmsc2 = MinMaxSumCountAggregator()
@@ -318,6 +389,128 @@ def test_merge_with_empty(self):
self.assertEqual(mmsc1.checkpoint, checkpoint1)
+ def test_concurrent_update(self):
+ mmsc = MinMaxSumCountAggregator()
+ with concurrent.futures.ThreadPoolExecutor(max_workers=2) as ex:
+ fut1 = ex.submit(self.call_update, mmsc)
+ fut2 = ex.submit(self.call_update, mmsc)
+
+ ret1 = fut1.result()
+ ret2 = fut2.result()
+
+ update_total = MinMaxSumCountAggregator._merge_checkpoint(
+ ret1, ret2
+ )
+ mmsc.take_checkpoint()
+
+ self.assertEqual(update_total, mmsc.checkpoint)
+
+ def test_concurrent_update_and_checkpoint(self):
+ mmsc = MinMaxSumCountAggregator()
+ checkpoint_total = MinMaxSumCountAggregator._TYPE(2 ** 32, 0, 0, 0)
+
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as ex:
+ fut = ex.submit(self.call_update, mmsc)
+
+ while not fut.done():
+ mmsc.take_checkpoint()
+ checkpoint_total = MinMaxSumCountAggregator._merge_checkpoint(
+ checkpoint_total, mmsc.checkpoint
+ )
+
+ mmsc.take_checkpoint()
+ checkpoint_total = MinMaxSumCountAggregator._merge_checkpoint(
+ checkpoint_total, mmsc.checkpoint
+ )
+
+ self.assertEqual(checkpoint_total, fut.result())
+
+
+class TestObserverAggregator(unittest.TestCase):
+ def test_update(self):
+ observer = ObserverAggregator()
+ # test current values without any update
+ self.assertEqual(
+ observer.mmsc.current, (None, None, None, 0),
+ )
+ self.assertIsNone(observer.current)
+
+ # call update with some values
+ values = (3, 50, 3, 97, 27)
+ for val in values:
+ observer.update(val)
+
+ self.assertEqual(
+ observer.mmsc.current,
+ (min(values), max(values), sum(values), len(values)),
+ )
+
+ self.assertEqual(observer.current, values[-1])
+
+ def test_checkpoint(self):
+ observer = ObserverAggregator()
+
+ # take checkpoint wihtout any update
+ observer.take_checkpoint()
+ self.assertEqual(
+ observer.checkpoint, (None, None, None, 0, None),
+ )
+
+ # call update with some values
+ values = (3, 50, 3, 97)
+ for val in values:
+ observer.update(val)
+
+ observer.take_checkpoint()
+ self.assertEqual(
+ observer.checkpoint,
+ (min(values), max(values), sum(values), len(values), values[-1]),
+ )
+
+ def test_merge(self):
+ observer1 = ObserverAggregator()
+ observer2 = ObserverAggregator()
+
+ mmsc_checkpoint1 = MinMaxSumCountAggregator._TYPE(3, 150, 101, 3)
+ mmsc_checkpoint2 = MinMaxSumCountAggregator._TYPE(1, 33, 44, 2)
+
+ checkpoint1 = ObserverAggregator._TYPE(*(mmsc_checkpoint1 + (23,)))
+
+ checkpoint2 = ObserverAggregator._TYPE(*(mmsc_checkpoint2 + (27,)))
+
+ observer1.mmsc.checkpoint = mmsc_checkpoint1
+ observer2.mmsc.checkpoint = mmsc_checkpoint2
+
+ observer1.checkpoint = checkpoint1
+ observer2.checkpoint = checkpoint2
+
+ observer1.merge(observer2)
+
+ self.assertEqual(
+ observer1.checkpoint,
+ (
+ min(checkpoint1.min, checkpoint2.min),
+ max(checkpoint1.max, checkpoint2.max),
+ checkpoint1.sum + checkpoint2.sum,
+ checkpoint1.count + checkpoint2.count,
+ checkpoint2.last,
+ ),
+ )
+
+ def test_merge_with_empty(self):
+ observer1 = ObserverAggregator()
+ observer2 = ObserverAggregator()
+
+ mmsc_checkpoint1 = MinMaxSumCountAggregator._TYPE(3, 150, 101, 3)
+ checkpoint1 = ObserverAggregator._TYPE(*(mmsc_checkpoint1 + (23,)))
+
+ observer1.mmsc.checkpoint = mmsc_checkpoint1
+ observer1.checkpoint = checkpoint1
+
+ observer1.merge(observer2)
+
+ self.assertEqual(observer1.checkpoint, checkpoint1)
+
class TestController(unittest.TestCase):
def test_push_controller(self):
diff --git a/opentelemetry-sdk/tests/metrics/test_implementation.py b/opentelemetry-sdk/tests/metrics/test_implementation.py
new file mode 100644
index 00000000000..1fedc9ae571
--- /dev/null
+++ b/opentelemetry-sdk/tests/metrics/test_implementation.py
@@ -0,0 +1,35 @@
+# Copyright 2020, 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 opentelemetry.metrics import DefaultLabelSet, DefaultMeter, DefaultMetric
+from opentelemetry.sdk import metrics
+
+
+class TestMeterImplementation(unittest.TestCase):
+ """
+ This test is in place to ensure the SDK implementation of the API
+ is returning values that are valid. The same tests have been added
+ to the API with different expected results. See issue for more details:
+ https://github.com/open-telemetry/opentelemetry-python/issues/142
+ """
+
+ def test_meter(self):
+ meter = metrics.MeterProvider().get_meter(__name__)
+ metric = meter.create_metric("", "", "", float, metrics.Counter)
+ label_set = meter.get_label_set({"key1": "val1"})
+ self.assertNotIsInstance(meter, DefaultMeter)
+ self.assertNotIsInstance(metric, DefaultMetric)
+ self.assertNotIsInstance(label_set, DefaultLabelSet)
diff --git a/opentelemetry-sdk/tests/metrics/test_metrics.py b/opentelemetry-sdk/tests/metrics/test_metrics.py
index db7e2d8c850..ea20cdd5930 100644
--- a/opentelemetry-sdk/tests/metrics/test_metrics.py
+++ b/opentelemetry-sdk/tests/metrics/test_metrics.py
@@ -1,4 +1,4 @@
-# Copyright 2019, OpenTelemetry Authors
+# Copyright 2020, OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -22,11 +22,11 @@
class TestMeter(unittest.TestCase):
def test_extends_api(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
self.assertIsInstance(meter, metrics_api.Meter)
def test_collect(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
batcher_mock = mock.Mock()
meter.batcher = batcher_mock
label_keys = ("key1",)
@@ -41,14 +41,14 @@ def test_collect(self):
self.assertTrue(batcher_mock.process.called)
def test_collect_no_metrics(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
batcher_mock = mock.Mock()
meter.batcher = batcher_mock
meter.collect()
self.assertFalse(batcher_mock.process.called)
def test_collect_disabled_metric(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
batcher_mock = mock.Mock()
meter.batcher = batcher_mock
label_keys = ("key1",)
@@ -62,8 +62,25 @@ def test_collect_disabled_metric(self):
meter.collect()
self.assertFalse(batcher_mock.process.called)
+ def test_collect_observers(self):
+ meter = metrics.MeterProvider().get_meter(__name__)
+ batcher_mock = mock.Mock()
+ meter.batcher = batcher_mock
+
+ def callback(observer):
+ self.assertIsInstance(observer, metrics_api.Observer)
+ observer.observe(45, meter.get_label_set(()))
+
+ observer = metrics.Observer(
+ callback, "name", "desc", "unit", int, meter, (), True
+ )
+
+ meter.observers.add(observer)
+ meter.collect()
+ self.assertTrue(batcher_mock.process.called)
+
def test_record_batch(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
label_keys = ("key1",)
counter = metrics.Counter(
"name", "desc", "unit", float, meter, label_keys
@@ -75,28 +92,26 @@ def test_record_batch(self):
self.assertEqual(counter.get_handle(label_set).aggregator.current, 1.0)
def test_record_batch_multiple(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
label_keys = ("key1", "key2", "key3")
kvp = {"key1": "value1", "key2": "value2", "key3": "value3"}
label_set = meter.get_label_set(kvp)
counter = metrics.Counter(
"name", "desc", "unit", float, meter, label_keys
)
- gauge = metrics.Gauge("name", "desc", "unit", int, meter, label_keys)
measure = metrics.Measure(
"name", "desc", "unit", float, meter, label_keys
)
- record_tuples = [(counter, 1.0), (gauge, 5), (measure, 3.0)]
+ record_tuples = [(counter, 1.0), (measure, 3.0)]
meter.record_batch(label_set, record_tuples)
self.assertEqual(counter.get_handle(label_set).aggregator.current, 1.0)
- self.assertEqual(gauge.get_handle(label_set).aggregator.current, 5.0)
self.assertEqual(
measure.get_handle(label_set).aggregator.current,
(3.0, 3.0, 3.0, 1),
)
def test_record_batch_exists(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
label_keys = ("key1",)
kvp = {"key1": "value1"}
label_set = meter.get_label_set(kvp)
@@ -111,34 +126,45 @@ def test_record_batch_exists(self):
self.assertEqual(handle.aggregator.current, 2.0)
def test_create_metric(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
counter = meter.create_metric(
"name", "desc", "unit", int, metrics.Counter, ()
)
- self.assertTrue(isinstance(counter, metrics.Counter))
+ self.assertIsInstance(counter, metrics.Counter)
self.assertEqual(counter.value_type, int)
self.assertEqual(counter.name, "name")
- def test_create_gauge(self):
- meter = metrics.Meter()
- gauge = meter.create_metric(
- "name", "desc", "unit", float, metrics.Gauge, ()
- )
- self.assertTrue(isinstance(gauge, metrics.Gauge))
- self.assertEqual(gauge.value_type, float)
- self.assertEqual(gauge.name, "name")
-
def test_create_measure(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
measure = meter.create_metric(
"name", "desc", "unit", float, metrics.Measure, ()
)
- self.assertTrue(isinstance(measure, metrics.Measure))
+ self.assertIsInstance(measure, metrics.Measure)
self.assertEqual(measure.value_type, float)
self.assertEqual(measure.name, "name")
+ def test_register_observer(self):
+ meter = metrics.MeterProvider().get_meter(__name__)
+
+ callback = mock.Mock()
+
+ observer = meter.register_observer(
+ callback, "name", "desc", "unit", int, (), True
+ )
+
+ self.assertIsInstance(observer, metrics_api.Observer)
+ self.assertEqual(len(meter.observers), 1)
+
+ self.assertEqual(observer.callback, callback)
+ self.assertEqual(observer.name, "name")
+ self.assertEqual(observer.description, "desc")
+ self.assertEqual(observer.unit, "unit")
+ self.assertEqual(observer.value_type, int)
+ self.assertEqual(observer.label_keys, ())
+ self.assertTrue(observer.enabled)
+
def test_get_label_set(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
kvp = {"environment": "staging", "a": "z"}
label_set = meter.get_label_set(kvp)
label_set2 = meter.get_label_set(kvp)
@@ -146,7 +172,7 @@ def test_get_label_set(self):
self.assertEqual(len(labels), 1)
def test_get_label_set_empty(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
kvp = {}
label_set = meter.get_label_set(kvp)
self.assertEqual(label_set, metrics.EMPTY_LABEL_SET)
@@ -154,8 +180,8 @@ def test_get_label_set_empty(self):
class TestMetric(unittest.TestCase):
def test_get_handle(self):
- meter = metrics.Meter()
- metric_types = [metrics.Counter, metrics.Gauge, metrics.Measure]
+ meter = metrics.MeterProvider().get_meter(__name__)
+ metric_types = [metrics.Counter, metrics.Measure]
for _type in metric_types:
metric = _type("name", "desc", "unit", int, meter, ("key",))
kvp = {"key": "value"}
@@ -166,7 +192,7 @@ def test_get_handle(self):
class TestCounter(unittest.TestCase):
def test_add(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
metric = metrics.Counter("name", "desc", "unit", int, meter, ("key",))
kvp = {"key": "value"}
label_set = meter.get_label_set(kvp)
@@ -176,23 +202,9 @@ def test_add(self):
self.assertEqual(handle.aggregator.current, 5)
-class TestGauge(unittest.TestCase):
- def test_set(self):
- meter = metrics.Meter()
- metric = metrics.Gauge("name", "desc", "unit", int, meter, ("key",))
- kvp = {"key": "value"}
- label_set = meter.get_label_set(kvp)
- handle = metric.get_handle(label_set)
- metric.set(3, label_set)
- self.assertEqual(handle.aggregator.current, 3)
- metric.set(2, label_set)
- # TODO: Fix once other aggregators implemented
- self.assertEqual(handle.aggregator.current, 5)
-
-
class TestMeasure(unittest.TestCase):
def test_record(self):
- meter = metrics.Meter()
+ meter = metrics.MeterProvider().get_meter(__name__)
metric = metrics.Measure("name", "desc", "unit", int, meter, ("key",))
kvp = {"key": "value"}
label_set = meter.get_label_set(kvp)
@@ -206,6 +218,72 @@ def test_record(self):
)
+class TestObserver(unittest.TestCase):
+ def test_observe(self):
+ meter = metrics.MeterProvider().get_meter(__name__)
+ observer = metrics.Observer(
+ None, "name", "desc", "unit", int, meter, ("key",), True
+ )
+ kvp = {"key": "value"}
+ label_set = meter.get_label_set(kvp)
+ values = (37, 42, 7, 21)
+ for val in values:
+ observer.observe(val, label_set)
+ self.assertEqual(
+ observer.aggregators[label_set].mmsc.current,
+ (min(values), max(values), sum(values), len(values)),
+ )
+
+ self.assertEqual(observer.aggregators[label_set].current, values[-1])
+
+ def test_observe_disabled(self):
+ meter = metrics.MeterProvider().get_meter(__name__)
+ observer = metrics.Observer(
+ None, "name", "desc", "unit", int, meter, ("key",), False
+ )
+ kvp = {"key": "value"}
+ label_set = meter.get_label_set(kvp)
+ observer.observe(37, label_set)
+ 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.Observer(
+ None, "name", "desc", "unit", int, meter, ("key",), True
+ )
+ kvp = {"key": "value"}
+ label_set = meter.get_label_set(kvp)
+ observer.observe(37.0, label_set)
+ 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.Observer(
+ 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.Observer(
+ callback, "name", "desc", "unit", int, meter, (), True
+ )
+
+ self.assertFalse(observer.run())
+ self.assertTrue(logger_mock.warning.called)
+
+
class TestCounterHandle(unittest.TestCase):
def test_add(self):
aggregator = export.aggregate.CounterAggregator()
@@ -237,38 +315,6 @@ def test_update(self, time_mock):
self.assertEqual(handle.aggregator.current, 4.0)
-# TODO: fix tests once aggregator implemented
-class TestGaugeHandle(unittest.TestCase):
- def test_set(self):
- aggregator = export.aggregate.CounterAggregator()
- handle = metrics.GaugeHandle(int, True, aggregator)
- handle.set(3)
- self.assertEqual(handle.aggregator.current, 3)
-
- def test_set_disabled(self):
- aggregator = export.aggregate.CounterAggregator()
- handle = metrics.GaugeHandle(int, False, aggregator)
- handle.set(3)
- self.assertEqual(handle.aggregator.current, 0)
-
- @mock.patch("opentelemetry.sdk.metrics.logger")
- def test_set_incorrect_type(self, logger_mock):
- aggregator = export.aggregate.CounterAggregator()
- handle = metrics.GaugeHandle(int, True, aggregator)
- handle.set(3.0)
- self.assertEqual(handle.aggregator.current, 0)
- self.assertTrue(logger_mock.warning.called)
-
- @mock.patch("opentelemetry.sdk.metrics.time_ns")
- def test_update(self, time_mock):
- aggregator = export.aggregate.CounterAggregator()
- handle = metrics.GaugeHandle(int, True, aggregator)
- time_mock.return_value = 123
- handle.update(4.0)
- self.assertEqual(handle.last_update_timestamp, 123)
- self.assertEqual(handle.aggregator.current, 4.0)
-
-
class TestMeasureHandle(unittest.TestCase):
def test_record(self):
aggregator = export.aggregate.MinMaxSumCountAggregator()
diff --git a/opentelemetry-sdk/tests/trace/export/test_export.py b/opentelemetry-sdk/tests/trace/export/test_export.py
index e598b9680a9..cedb5967666 100644
--- a/opentelemetry-sdk/tests/trace/export/test_export.py
+++ b/opentelemetry-sdk/tests/trace/export/test_export.py
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import os
import time
import unittest
from logging import WARNING
@@ -52,14 +53,14 @@ def shutdown(self):
class TestSimpleExportSpanProcessor(unittest.TestCase):
def test_simple_span_processor(self):
- tracer_source = trace.TracerSource()
- tracer = tracer_source.get_tracer(__name__)
+ tracer_provider = trace.TracerProvider()
+ tracer = tracer_provider.get_tracer(__name__)
spans_names_list = []
my_exporter = MySpanExporter(destination=spans_names_list)
span_processor = export.SimpleExportSpanProcessor(my_exporter)
- tracer_source.add_span_processor(span_processor)
+ tracer_provider.add_span_processor(span_processor)
with tracer.start_as_current_span("foo"):
with tracer.start_as_current_span("bar"):
@@ -77,14 +78,14 @@ def test_simple_span_processor_no_context(self):
SpanProcessors should act on a span's start and end events whether or
not it is ever the active span.
"""
- tracer_source = trace.TracerSource()
- tracer = tracer_source.get_tracer(__name__)
+ tracer_provider = trace.TracerProvider()
+ tracer = tracer_provider.get_tracer(__name__)
spans_names_list = []
my_exporter = MySpanExporter(destination=spans_names_list)
span_processor = export.SimpleExportSpanProcessor(my_exporter)
- tracer_source.add_span_processor(span_processor)
+ tracer_provider.add_span_processor(span_processor)
with tracer.start_span("foo"):
with tracer.start_span("bar"):
@@ -288,8 +289,9 @@ def test_export(self): # pylint: disable=no-self-use
span = trace.Span("span name", mock.Mock())
with mock.patch.object(exporter, "out") as mock_stdout:
exporter.export([span])
- mock_stdout.write.assert_called_once_with(str(span))
+ mock_stdout.write.assert_called_once_with(str(span) + os.linesep)
self.assertEqual(mock_stdout.write.call_count, 1)
+ self.assertEqual(mock_stdout.flush.call_count, 1)
def test_export_custom(self): # pylint: disable=no-self-use
"""Check that console exporter uses custom io, formatter."""
diff --git a/opentelemetry-sdk/tests/trace/export/test_in_memory_span_exporter.py b/opentelemetry-sdk/tests/trace/export/test_in_memory_span_exporter.py
index 5c5194053bb..45b65fb3726 100644
--- a/opentelemetry-sdk/tests/trace/export/test_in_memory_span_exporter.py
+++ b/opentelemetry-sdk/tests/trace/export/test_in_memory_span_exporter.py
@@ -25,11 +25,11 @@
class TestInMemorySpanExporter(unittest.TestCase):
def setUp(self):
- self.tracer_source = trace.TracerSource()
- self.tracer = self.tracer_source.get_tracer(__name__)
+ self.tracer_provider = trace.TracerProvider()
+ self.tracer = self.tracer_provider.get_tracer(__name__)
self.memory_exporter = InMemorySpanExporter()
span_processor = export.SimpleExportSpanProcessor(self.memory_exporter)
- self.tracer_source.add_span_processor(span_processor)
+ self.tracer_provider.add_span_processor(span_processor)
self.exec_scenario()
def exec_scenario(self):
diff --git a/opentelemetry-sdk/tests/test_implementation.py b/opentelemetry-sdk/tests/trace/test_implementation.py
similarity index 82%
rename from opentelemetry-sdk/tests/test_implementation.py
rename to opentelemetry-sdk/tests/trace/test_implementation.py
index d8d6bae1393..74d3d5a9232 100644
--- a/opentelemetry-sdk/tests/test_implementation.py
+++ b/opentelemetry-sdk/tests/trace/test_implementation.py
@@ -14,12 +14,11 @@
import unittest
-from opentelemetry.metrics import DefaultMetric
-from opentelemetry.sdk import metrics, trace
+from opentelemetry.sdk import trace
from opentelemetry.trace import INVALID_SPAN, INVALID_SPAN_CONTEXT
-class TestSDKImplementation(unittest.TestCase):
+class TestTracerImplementation(unittest.TestCase):
"""
This test is in place to ensure the SDK implementation of the API
is returning values that are valid. The same tests have been added
@@ -28,7 +27,7 @@ class TestSDKImplementation(unittest.TestCase):
"""
def test_tracer(self):
- tracer = trace.TracerSource().get_tracer(__name__)
+ tracer = trace.TracerProvider().get_tracer(__name__)
with tracer.start_span("test") as span:
self.assertNotEqual(span.get_context(), INVALID_SPAN_CONTEXT)
self.assertNotEqual(span, INVALID_SPAN)
@@ -46,8 +45,3 @@ def test_span(self):
span = trace.Span("name", INVALID_SPAN_CONTEXT)
self.assertEqual(span.get_context(), INVALID_SPAN_CONTEXT)
self.assertIs(span.is_recording_events(), True)
-
- def test_meter(self):
- meter = metrics.Meter()
- metric = meter.create_metric("", "", "", float, metrics.Counter)
- self.assertNotIsInstance(metric, DefaultMetric)
diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py
index fa6ee3cf271..6c934e7e4be 100644
--- a/opentelemetry-sdk/tests/trace/test_trace.py
+++ b/opentelemetry-sdk/tests/trace/test_trace.py
@@ -20,13 +20,14 @@
from opentelemetry import trace as trace_api
from opentelemetry.sdk import trace
+from opentelemetry.sdk.util.instrumentation import InstrumentationInfo
from opentelemetry.trace import sampling
from opentelemetry.trace.status import StatusCanonicalCode
from opentelemetry.util import time_ns
-def new_tracer() -> trace_api.Tracer:
- return trace.TracerSource().get_tracer(__name__)
+def new_tracer(name: str = __name__) -> trace_api.Tracer:
+ return trace.TracerProvider().get_tracer(name)
class TestTracer(unittest.TestCase):
@@ -36,15 +37,15 @@ def test_extends_api(self):
self.assertIsInstance(tracer, trace_api.Tracer)
def test_shutdown(self):
- tracer_source = trace.TracerSource()
+ tracer_provider = trace.TracerProvider()
mock_processor1 = mock.Mock(spec=trace.SpanProcessor)
- tracer_source.add_span_processor(mock_processor1)
+ tracer_provider.add_span_processor(mock_processor1)
mock_processor2 = mock.Mock(spec=trace.SpanProcessor)
- tracer_source.add_span_processor(mock_processor2)
+ tracer_provider.add_span_processor(mock_processor2)
- tracer_source.shutdown()
+ tracer_provider.shutdown()
self.assertEqual(mock_processor1.shutdown.call_count, 1)
self.assertEqual(mock_processor2.shutdown.call_count, 1)
@@ -64,8 +65,8 @@ def print_shutdown_count():
# creating the tracer
atexit.register(print_shutdown_count)
-tracer_source = trace.TracerSource({tracer_parameters})
-tracer_source.add_span_processor(mock_processor)
+tracer_provider = trace.TracerProvider({tracer_parameters})
+tracer_provider.add_span_processor(mock_processor)
{tracer_shutdown}
"""
@@ -78,7 +79,7 @@ def run_general_code(shutdown_on_exit, explicit_shutdown):
tracer_parameters = "shutdown_on_exit=False"
if explicit_shutdown:
- tracer_shutdown = "tracer_source.shutdown()"
+ tracer_shutdown = "tracer_provider.shutdown()"
return subprocess.check_output(
[
@@ -117,11 +118,11 @@ def test_default_sampler(self):
self.assertIsInstance(root_span, trace.Span)
child_span = tracer.start_span(name="child span", parent=root_span)
self.assertIsInstance(child_span, trace.Span)
- self.assertTrue(root_span.context.trace_options.sampled)
+ self.assertTrue(root_span.context.trace_flags.sampled)
def test_sampler_no_sampling(self):
- tracer_source = trace.TracerSource(sampling.ALWAYS_OFF)
- tracer = tracer_source.get_tracer(__name__)
+ tracer_provider = trace.TracerProvider(sampling.ALWAYS_OFF)
+ tracer = tracer_provider.get_tracer(__name__)
# Check that the default tracer creates no-op spans if the sampler
# decides not to sampler
@@ -147,17 +148,16 @@ def test_start_span_invalid_spancontext(self):
self.assertIsNone(new_span.parent)
def test_instrumentation_info(self):
- tracer_source = trace.TracerSource()
- tracer1 = tracer_source.get_tracer("instr1")
- tracer2 = tracer_source.get_tracer("instr2", "1.3b3")
+ tracer_provider = trace.TracerProvider()
+ tracer1 = tracer_provider.get_tracer("instr1")
+ tracer2 = tracer_provider.get_tracer("instr2", "1.3b3")
span1 = tracer1.start_span("s1")
span2 = tracer2.start_span("s2")
self.assertEqual(
- span1.instrumentation_info, trace.InstrumentationInfo("instr1", "")
+ span1.instrumentation_info, InstrumentationInfo("instr1", "")
)
self.assertEqual(
- span2.instrumentation_info,
- trace.InstrumentationInfo("instr2", "1.3b3"),
+ span2.instrumentation_info, InstrumentationInfo("instr2", "1.3b3")
)
self.assertEqual(span2.instrumentation_info.version, "1.3b3")
@@ -168,16 +168,16 @@ def test_instrumentation_info(self):
) # Check sortability.
def test_invalid_instrumentation_info(self):
- tracer_source = trace.TracerSource()
+ tracer_provider = trace.TracerProvider()
with self.assertLogs(level=ERROR):
- tracer1 = tracer_source.get_tracer("")
+ tracer1 = tracer_provider.get_tracer("")
with self.assertLogs(level=ERROR):
- tracer2 = tracer_source.get_tracer(None)
+ tracer2 = tracer_provider.get_tracer(None)
self.assertEqual(
tracer1.instrumentation_info, tracer2.instrumentation_info
)
self.assertIsInstance(
- tracer1.instrumentation_info, trace.InstrumentationInfo
+ tracer1.instrumentation_info, InstrumentationInfo
)
span1 = tracer1.start_span("foo")
self.assertTrue(span1.is_recording_events())
@@ -187,184 +187,43 @@ def test_invalid_instrumentation_info(self):
)
def test_span_processor_for_source(self):
- tracer_source = trace.TracerSource()
- tracer1 = tracer_source.get_tracer("instr1")
- tracer2 = tracer_source.get_tracer("instr2", "1.3b3")
+ tracer_provider = trace.TracerProvider()
+ tracer1 = tracer_provider.get_tracer("instr1")
+ tracer2 = tracer_provider.get_tracer("instr2", "1.3b3")
span1 = tracer1.start_span("s1")
span2 = tracer2.start_span("s2")
# pylint:disable=protected-access
self.assertIs(
- span1.span_processor, tracer_source._active_span_processor
+ span1.span_processor, tracer_provider._active_span_processor
)
self.assertIs(
- span2.span_processor, tracer_source._active_span_processor
+ 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")
+ Regardless of the tracer or tracerprovider,
+ all tracers should share the same span.
+
+ If one uses the span, get_current_span should
+ return back the used span.
+ """
+ tracer_provider_1 = trace.TracerProvider()
+ tracer_provider_2 = trace.TracerProvider()
+ tracer_1 = tracer_provider_1.get_tracer("foo")
+ tracer_2 = tracer_provider_2.get_tracer("bar")
+ tracer_3 = tracer_provider_2.get_tracer("baz")
+ root = trace.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)
+ self.assertIs(tracer_3.get_current_span(), root)
- # outside of the loop, both should not reference a span.
+ # outside of the loop, all 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())
-
- root = tracer.start_span("root")
- self.assertIsNotNone(root.start_time)
- self.assertIsNone(root.end_time)
- self.assertEqual(root.kind, trace_api.SpanKind.INTERNAL)
-
- with tracer.use_span(root, True):
- self.assertIs(tracer.get_current_span(), root)
-
- with tracer.start_span(
- "child", kind=trace_api.SpanKind.CLIENT
- ) as child:
- self.assertIs(child.parent, root)
- self.assertEqual(child.kind, trace_api.SpanKind.CLIENT)
-
- self.assertIsNotNone(child.start_time)
- self.assertIsNone(child.end_time)
-
- # The new child span should inherit the parent's context but
- # get a new span ID.
- root_context = root.get_context()
- child_context = child.get_context()
- self.assertEqual(root_context.trace_id, child_context.trace_id)
- self.assertNotEqual(
- root_context.span_id, child_context.span_id
- )
- self.assertEqual(
- root_context.trace_state, child_context.trace_state
- )
- self.assertEqual(
- root_context.trace_options, child_context.trace_options
- )
-
- # Verify start_span() did not set the current span.
- self.assertIs(tracer.get_current_span(), root)
-
- self.assertIsNotNone(child.end_time)
-
- self.assertIsNone(tracer.get_current_span())
- self.assertIsNotNone(root.end_time)
-
- def test_start_span_explicit(self):
- tracer = new_tracer()
-
- other_parent = trace_api.SpanContext(
- trace_id=0x000000000000000000000000DEADBEEF,
- span_id=0x00000000DEADBEF0,
- trace_options=trace_api.TraceOptions(
- trace_api.TraceOptions.SAMPLED
- ),
- )
-
- self.assertIsNone(tracer.get_current_span())
-
- root = tracer.start_span("root")
- self.assertIsNotNone(root.start_time)
- self.assertIsNone(root.end_time)
-
- # Test with the implicit root span
- with tracer.use_span(root, True):
- self.assertIs(tracer.get_current_span(), root)
-
- with tracer.start_span("stepchild", other_parent) as child:
- # The child's parent should be the one passed in,
- # not the current span.
- self.assertNotEqual(child.parent, root)
- self.assertIs(child.parent, other_parent)
-
- self.assertIsNotNone(child.start_time)
- self.assertIsNone(child.end_time)
-
- # The child should inherit its context from the explicit
- # parent, not the current span.
- child_context = child.get_context()
- self.assertEqual(other_parent.trace_id, child_context.trace_id)
- self.assertNotEqual(
- other_parent.span_id, child_context.span_id
- )
- self.assertEqual(
- other_parent.trace_state, child_context.trace_state
- )
- self.assertEqual(
- other_parent.trace_options, child_context.trace_options
- )
-
- # Verify start_span() did not set the current span.
- self.assertIs(tracer.get_current_span(), root)
-
- # Verify ending the child did not set the current span.
- self.assertIs(tracer.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())
-
- with tracer.start_as_current_span("root") as root:
- self.assertIs(tracer.get_current_span(), root)
-
- with tracer.start_as_current_span("child") as child:
- self.assertIs(tracer.get_current_span(), child)
- self.assertIs(child.parent, root)
-
- # After exiting the child's scope the parent should become the
- # current span again.
- self.assertIs(tracer.get_current_span(), root)
- self.assertIsNotNone(child.end_time)
-
- self.assertIsNone(tracer.get_current_span())
- self.assertIsNotNone(root.end_time)
-
- def test_start_as_current_span_explicit(self):
- tracer = new_tracer()
-
- other_parent = trace_api.SpanContext(
- trace_id=0x000000000000000000000000DEADBEEF,
- span_id=0x00000000DEADBEF0,
- )
-
- self.assertIsNone(tracer.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.assertIsNotNone(root.start_time)
- self.assertIsNone(root.end_time)
-
- with tracer.start_as_current_span(
- "stepchild", other_parent
- ) as child:
- # 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.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.assertIsNotNone(child.end_time)
+ self.assertIs(tracer_3.get_current_span(), None)
class TestSpan(unittest.TestCase):
@@ -472,13 +331,13 @@ def test_sampling_attributes(self):
"sampler-attr": "sample-val",
"attr-in-both": "decision-attr",
}
- tracer_source = trace.TracerSource(
+ tracer_provider = trace.TracerProvider(
sampling.StaticSampler(
sampling.Decision(sampled=True, attributes=decision_attributes)
)
)
- self.tracer = tracer_source.get_tracer(__name__)
+ self.tracer = tracer_provider.get_tracer(__name__)
with self.tracer.start_as_current_span("root2") as root:
self.assertEqual(len(root.attributes), 2)
@@ -581,32 +440,6 @@ def test_update_name(self):
root.update_name("toor")
self.assertEqual(root.name, "toor")
- def test_start_span(self):
- """Start twice, end a not started"""
- span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext))
-
- # end not started span
- self.assertRaises(RuntimeError, span.end)
-
- span.start()
- start_time = span.start_time
- with self.assertLogs(level=WARNING):
- span.start()
- self.assertEqual(start_time, span.start_time)
-
- self.assertIs(span.status, None)
-
- # status
- new_status = trace_api.status.Status(
- trace_api.status.StatusCanonicalCode.CANCELLED, "Test description"
- )
- span.set_status(new_status)
- self.assertIs(
- span.status.canonical_code,
- trace_api.status.StatusCanonicalCode.CANCELLED,
- )
- self.assertIs(span.status.description, "Test description")
-
def test_span_override_start_and_end_time(self):
"""Span sending custom start_time and end_time values"""
span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext))
@@ -673,10 +506,10 @@ def error_status_test(context):
)
error_status_test(
- trace.TracerSource().get_tracer(__name__).start_span("root")
+ trace.TracerProvider().get_tracer(__name__).start_span("root")
)
error_status_test(
- trace.TracerSource()
+ trace.TracerProvider()
.get_tracer(__name__)
.start_as_current_span("root")
)
@@ -704,8 +537,8 @@ def on_end(self, span: "trace.Span") -> None:
class TestSpanProcessor(unittest.TestCase):
def test_span_processor(self):
- tracer_source = trace.TracerSource()
- tracer = tracer_source.get_tracer(__name__)
+ tracer_provider = trace.TracerProvider()
+ tracer = tracer_provider.get_tracer(__name__)
spans_calls_list = [] # filled by MySpanProcessor
expected_list = [] # filled by hand
@@ -723,7 +556,7 @@ def test_span_processor(self):
self.assertEqual(len(spans_calls_list), 0)
# add single span processor
- tracer_source.add_span_processor(sp1)
+ tracer_provider.add_span_processor(sp1)
with tracer.start_as_current_span("foo"):
expected_list.append(span_event_start_fmt("SP1", "foo"))
@@ -746,7 +579,7 @@ def test_span_processor(self):
expected_list.clear()
# go for multiple span processors
- tracer_source.add_span_processor(sp2)
+ tracer_provider.add_span_processor(sp2)
with tracer.start_as_current_span("foo"):
expected_list.append(span_event_start_fmt("SP1", "foo"))
@@ -773,8 +606,8 @@ def test_span_processor(self):
self.assertListEqual(spans_calls_list, expected_list)
def test_add_span_processor_after_span_creation(self):
- tracer_source = trace.TracerSource()
- tracer = tracer_source.get_tracer(__name__)
+ tracer_provider = trace.TracerProvider()
+ tracer = tracer_provider.get_tracer(__name__)
spans_calls_list = [] # filled by MySpanProcessor
expected_list = [] # filled by hand
@@ -786,7 +619,7 @@ def test_add_span_processor_after_span_creation(self):
with tracer.start_as_current_span("bar"):
with tracer.start_as_current_span("baz"):
# add span processor after spans have been created
- tracer_source.add_span_processor(sp)
+ tracer_provider.add_span_processor(sp)
expected_list.append(span_event_end_fmt("SP1", "baz"))
diff --git a/tests/w3c_tracecontext_validation_server.py b/tests/w3c_tracecontext_validation_server.py
index bea4d4fde55..4ec179c3545 100644
--- a/tests/w3c_tracecontext_validation_server.py
+++ b/tests/w3c_tracecontext_validation_server.py
@@ -26,7 +26,7 @@
from opentelemetry import trace
from opentelemetry.ext import http_requests
from opentelemetry.ext.wsgi import OpenTelemetryMiddleware
-from opentelemetry.sdk.trace import TracerSource
+from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
ConsoleSpanExporter,
SimpleExportSpanProcessor,
@@ -34,16 +34,16 @@
# The preferred tracer implementation must be set, as the opentelemetry-api
# defines the interface with a no-op implementation.
-trace.set_preferred_tracer_source_implementation(lambda T: TracerSource())
+trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
# Integrations are the glue that binds the OpenTelemetry API and the
# frameworks and libraries that are used together, automatically creating
# Spans and propagating context as appropriate.
-http_requests.enable(trace.tracer_source())
+http_requests.enable(trace.tracer_provider())
# SpanExporter receives the spans and send them to the target location.
span_processor = SimpleExportSpanProcessor(ConsoleSpanExporter())
-trace.tracer_source().add_span_processor(span_processor)
+trace.tracer_provider().add_span_processor(span_processor)
app = flask.Flask(__name__)
app.wsgi_app = OpenTelemetryMiddleware(app.wsgi_app)
diff --git a/tox.ini b/tox.ini
index be7f1db9f73..0423a6cc7ea 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,7 +2,6 @@
skipsdist = True
skip_missing_interpreters = True
envlist =
-
; Environments are organized by individual package, allowing
; for specifying supported Python versions per package.
; opentelemetry-api
@@ -44,7 +43,10 @@ envlist =
; opentelemetry-ext-mysql
py3{4,5,6,7,8}-test-ext-mysql
pypy3-test-ext-mysql
-
+ ; opentelemetry-ext-otcollector
+ py3{4,5,6,7,8}-test-ext-otcollector
+ ; ext-otcollector intentionally excluded from pypy3
+
; opentelemetry-ext-prometheus
py3{4,5,6,7,8}-test-ext-prometheus
pypy3-test-ext-prometheus
@@ -103,6 +105,7 @@ changedir =
test-ext-jaeger: ext/opentelemetry-ext-jaeger/tests
test-ext-dbapi: ext/opentelemetry-ext-dbapi/tests
test-ext-mysql: ext/opentelemetry-ext-mysql/tests
+ test-ext-otcollector: ext/opentelemetry-ext-otcollector/tests
test-ext-prometheus: ext/opentelemetry-ext-prometheus/tests
test-ext-pymongo: ext/opentelemetry-ext-pymongo/tests
test-ext-psycopg2: ext/opentelemetry-ext-psycopg2/tests
@@ -140,6 +143,8 @@ commands_pre =
dbapi: pip install {toxinidir}/ext/opentelemetry-ext-dbapi
mysql: pip install {toxinidir}/ext/opentelemetry-ext-dbapi
mysql: pip install {toxinidir}/ext/opentelemetry-ext-mysql
+ otcollector: pip install {toxinidir}/opentelemetry-sdk
+ otcollector: pip install {toxinidir}/ext/opentelemetry-ext-otcollector
prometheus: pip install {toxinidir}/opentelemetry-sdk
prometheus: pip install {toxinidir}/ext/opentelemetry-ext-prometheus
pymongo: pip install {toxinidir}/ext/opentelemetry-ext-pymongo
@@ -182,6 +187,7 @@ deps =
flake8
isort
black
+ psutil
commands_pre =
python scripts/eachdist.py install --editable
@@ -192,14 +198,16 @@ commands =
[testenv:docs]
deps =
-c dev-requirements.txt
+ -c docs-requirements.txt
sphinx
sphinx-rtd-theme
sphinx-autodoc-typehints
- opentracing~=2.2.0
- Deprecated>=1.2.6
- thrift>=0.10.0
- pymongo ~= 3.1
- flask~=1.0
+ # Required by ext packages
+ opentracing
+ Deprecated
+ thrift
+ pymongo
+ flask
changedir = docs
@@ -231,7 +239,7 @@ deps =
docker-compose >= 1.25.2
pymongo ~= 3.1
-changedir =
+changedir =
ext/opentelemetry-ext-docker-tests/tests
commands_pre =
@@ -239,8 +247,8 @@ commands_pre =
-e {toxinidir}/opentelemetry-sdk \
-e {toxinidir}/ext/opentelemetry-ext-pymongo
- docker-compose up -d
-commands =
+commands =
pytest {posargs}
commands_post =
- docker-compose down
\ No newline at end of file
+ docker-compose down