mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-07-30 21:56:07 +08:00
Rename remaining framework packages from "ext" to "instrumentation" (#969)
This commit is contained in:
@ -0,0 +1,277 @@
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
import asyncpg
|
||||
|
||||
from opentelemetry.instrumentation.asyncpg import AsyncPGInstrumentor
|
||||
from opentelemetry.test.test_base import TestBase
|
||||
from opentelemetry.trace.status import StatusCanonicalCode
|
||||
|
||||
POSTGRES_HOST = os.getenv("POSTGRESQL_HOST ", "localhost")
|
||||
POSTGRES_PORT = int(os.getenv("POSTGRESQL_PORT ", "5432"))
|
||||
POSTGRES_DB_NAME = os.getenv("POSTGRESQL_DB_NAME ", "opentelemetry-tests")
|
||||
POSTGRES_PASSWORD = os.getenv("POSTGRESQL_HOST ", "testpassword")
|
||||
POSTGRES_USER = os.getenv("POSTGRESQL_HOST ", "testuser")
|
||||
|
||||
|
||||
def _await(coro):
|
||||
loop = asyncio.get_event_loop()
|
||||
return loop.run_until_complete(coro)
|
||||
|
||||
|
||||
class TestFunctionalAsyncPG(TestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._connection = None
|
||||
cls._cursor = None
|
||||
cls._tracer = cls.tracer_provider.get_tracer(__name__)
|
||||
AsyncPGInstrumentor().instrument(tracer_provider=cls.tracer_provider)
|
||||
cls._connection = _await(
|
||||
asyncpg.connect(
|
||||
database=POSTGRES_DB_NAME,
|
||||
user=POSTGRES_USER,
|
||||
password=POSTGRES_PASSWORD,
|
||||
host=POSTGRES_HOST,
|
||||
port=POSTGRES_PORT,
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
AsyncPGInstrumentor().uninstrument()
|
||||
|
||||
def test_instrumented_execute_method_without_arguments(self, *_, **__):
|
||||
_await(self._connection.execute("SELECT 42;"))
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
self.assertEqual(
|
||||
StatusCanonicalCode.OK, spans[0].status.canonical_code
|
||||
)
|
||||
self.assertEqual(
|
||||
spans[0].attributes,
|
||||
{
|
||||
"db.type": "sql",
|
||||
"db.user": POSTGRES_USER,
|
||||
"db.instance": POSTGRES_DB_NAME,
|
||||
"db.statement": "SELECT 42;",
|
||||
},
|
||||
)
|
||||
|
||||
def test_instrumented_fetch_method_without_arguments(self, *_, **__):
|
||||
_await(self._connection.fetch("SELECT 42;"))
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
self.assertEqual(
|
||||
spans[0].attributes,
|
||||
{
|
||||
"db.type": "sql",
|
||||
"db.user": POSTGRES_USER,
|
||||
"db.instance": POSTGRES_DB_NAME,
|
||||
"db.statement": "SELECT 42;",
|
||||
},
|
||||
)
|
||||
|
||||
def test_instrumented_transaction_method(self, *_, **__):
|
||||
async def _transaction_execute():
|
||||
async with self._connection.transaction():
|
||||
await self._connection.execute("SELECT 42;")
|
||||
|
||||
_await(_transaction_execute())
|
||||
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(3, len(spans))
|
||||
self.assertEqual(
|
||||
{
|
||||
"db.instance": POSTGRES_DB_NAME,
|
||||
"db.user": POSTGRES_USER,
|
||||
"db.type": "sql",
|
||||
"db.statement": "BEGIN;",
|
||||
},
|
||||
spans[0].attributes,
|
||||
)
|
||||
self.assertEqual(
|
||||
StatusCanonicalCode.OK, spans[0].status.canonical_code
|
||||
)
|
||||
self.assertEqual(
|
||||
{
|
||||
"db.instance": POSTGRES_DB_NAME,
|
||||
"db.user": POSTGRES_USER,
|
||||
"db.type": "sql",
|
||||
"db.statement": "SELECT 42;",
|
||||
},
|
||||
spans[1].attributes,
|
||||
)
|
||||
self.assertEqual(
|
||||
StatusCanonicalCode.OK, spans[1].status.canonical_code
|
||||
)
|
||||
self.assertEqual(
|
||||
{
|
||||
"db.instance": POSTGRES_DB_NAME,
|
||||
"db.user": POSTGRES_USER,
|
||||
"db.type": "sql",
|
||||
"db.statement": "COMMIT;",
|
||||
},
|
||||
spans[2].attributes,
|
||||
)
|
||||
self.assertEqual(
|
||||
StatusCanonicalCode.OK, spans[2].status.canonical_code
|
||||
)
|
||||
|
||||
def test_instrumented_failed_transaction_method(self, *_, **__):
|
||||
async def _transaction_execute():
|
||||
async with self._connection.transaction():
|
||||
await self._connection.execute("SELECT 42::uuid;")
|
||||
|
||||
with self.assertRaises(asyncpg.CannotCoerceError):
|
||||
_await(_transaction_execute())
|
||||
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(3, len(spans))
|
||||
self.assertEqual(
|
||||
{
|
||||
"db.instance": POSTGRES_DB_NAME,
|
||||
"db.user": POSTGRES_USER,
|
||||
"db.type": "sql",
|
||||
"db.statement": "BEGIN;",
|
||||
},
|
||||
spans[0].attributes,
|
||||
)
|
||||
self.assertEqual(
|
||||
StatusCanonicalCode.OK, spans[0].status.canonical_code
|
||||
)
|
||||
self.assertEqual(
|
||||
{
|
||||
"db.instance": POSTGRES_DB_NAME,
|
||||
"db.user": POSTGRES_USER,
|
||||
"db.type": "sql",
|
||||
"db.statement": "SELECT 42::uuid;",
|
||||
},
|
||||
spans[1].attributes,
|
||||
)
|
||||
self.assertEqual(
|
||||
StatusCanonicalCode.INVALID_ARGUMENT,
|
||||
spans[1].status.canonical_code,
|
||||
)
|
||||
self.assertEqual(
|
||||
{
|
||||
"db.instance": POSTGRES_DB_NAME,
|
||||
"db.user": POSTGRES_USER,
|
||||
"db.type": "sql",
|
||||
"db.statement": "ROLLBACK;",
|
||||
},
|
||||
spans[2].attributes,
|
||||
)
|
||||
self.assertEqual(
|
||||
StatusCanonicalCode.OK, spans[2].status.canonical_code
|
||||
)
|
||||
|
||||
def test_instrumented_method_doesnt_capture_parameters(self, *_, **__):
|
||||
_await(self._connection.execute("SELECT $1;", "1"))
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
self.assertEqual(
|
||||
StatusCanonicalCode.OK, spans[0].status.canonical_code
|
||||
)
|
||||
self.assertEqual(
|
||||
spans[0].attributes,
|
||||
{
|
||||
"db.type": "sql",
|
||||
"db.user": POSTGRES_USER,
|
||||
# This shouldn't be set because we don't capture parameters by
|
||||
# default
|
||||
#
|
||||
# "db.statement.parameters": "('1',)",
|
||||
"db.instance": POSTGRES_DB_NAME,
|
||||
"db.statement": "SELECT $1;",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class TestFunctionalAsyncPG_CaptureParameters(TestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._connection = None
|
||||
cls._cursor = None
|
||||
cls._tracer = cls.tracer_provider.get_tracer(__name__)
|
||||
AsyncPGInstrumentor(capture_parameters=True).instrument(
|
||||
tracer_provider=cls.tracer_provider
|
||||
)
|
||||
cls._connection = _await(
|
||||
asyncpg.connect(
|
||||
database=POSTGRES_DB_NAME,
|
||||
user=POSTGRES_USER,
|
||||
password=POSTGRES_PASSWORD,
|
||||
host=POSTGRES_HOST,
|
||||
port=POSTGRES_PORT,
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
AsyncPGInstrumentor().uninstrument()
|
||||
|
||||
def test_instrumented_execute_method_with_arguments(self, *_, **__):
|
||||
_await(self._connection.execute("SELECT $1;", "1"))
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
self.assertEqual(
|
||||
StatusCanonicalCode.OK, spans[0].status.canonical_code
|
||||
)
|
||||
self.assertEqual(
|
||||
spans[0].attributes,
|
||||
{
|
||||
"db.type": "sql",
|
||||
"db.user": POSTGRES_USER,
|
||||
"db.statement.parameters": "('1',)",
|
||||
"db.instance": POSTGRES_DB_NAME,
|
||||
"db.statement": "SELECT $1;",
|
||||
},
|
||||
)
|
||||
|
||||
def test_instrumented_fetch_method_with_arguments(self, *_, **__):
|
||||
_await(self._connection.fetch("SELECT $1;", "1"))
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
self.assertEqual(
|
||||
spans[0].attributes,
|
||||
{
|
||||
"db.type": "sql",
|
||||
"db.user": POSTGRES_USER,
|
||||
"db.statement.parameters": "('1',)",
|
||||
"db.instance": POSTGRES_DB_NAME,
|
||||
"db.statement": "SELECT $1;",
|
||||
},
|
||||
)
|
||||
|
||||
def test_instrumented_executemany_method_with_arguments(self, *_, **__):
|
||||
_await(self._connection.executemany("SELECT $1;", [["1"], ["2"]]))
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
self.assertEqual(
|
||||
{
|
||||
"db.type": "sql",
|
||||
"db.statement": "SELECT $1;",
|
||||
"db.statement.parameters": "([['1'], ['2']],)",
|
||||
"db.user": POSTGRES_USER,
|
||||
"db.instance": POSTGRES_DB_NAME,
|
||||
},
|
||||
spans[0].attributes,
|
||||
)
|
||||
|
||||
def test_instrumented_execute_interface_error_method(self, *_, **__):
|
||||
with self.assertRaises(asyncpg.InterfaceError):
|
||||
_await(self._connection.execute("SELECT 42;", 1, 2, 3))
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
self.assertEqual(
|
||||
spans[0].attributes,
|
||||
{
|
||||
"db.type": "sql",
|
||||
"db.instance": POSTGRES_DB_NAME,
|
||||
"db.user": POSTGRES_USER,
|
||||
"db.statement.parameters": "(1, 2, 3)",
|
||||
"db.statement": "SELECT 42;",
|
||||
},
|
||||
)
|
92
tests/opentelemetry-docker-tests/tests/celery/conftest.py
Normal file
92
tests/opentelemetry-docker-tests/tests/celery/conftest.py
Normal file
@ -0,0 +1,92 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
from functools import wraps
|
||||
|
||||
import pytest
|
||||
|
||||
from opentelemetry import trace as trace_api
|
||||
from opentelemetry.instrumentation.celery import CeleryInstrumentor
|
||||
from opentelemetry.sdk.trace import TracerProvider, export
|
||||
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
|
||||
InMemorySpanExporter,
|
||||
)
|
||||
|
||||
REDIS_HOST = os.getenv("REDIS_HOST", "localhost")
|
||||
REDIS_PORT = int(os.getenv("REDIS_PORT ", "6379"))
|
||||
REDIS_URL = "redis://{host}:{port}".format(host=REDIS_HOST, port=REDIS_PORT)
|
||||
BROKER_URL = "{redis}/{db}".format(redis=REDIS_URL, db=0)
|
||||
BACKEND_URL = "{redis}/{db}".format(redis=REDIS_URL, db=1)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def celery_config():
|
||||
return {"broker_url": BROKER_URL, "result_backend": BACKEND_URL}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def celery_worker_parameters():
|
||||
return {
|
||||
# See https://github.com/celery/celery/issues/3642#issuecomment-457773294
|
||||
"perform_ping_check": False,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patch_celery_app(celery_app, celery_worker):
|
||||
"""Patch task decorator on app fixture to reload worker"""
|
||||
# See https://github.com/celery/celery/issues/3642
|
||||
def wrap_task(fn):
|
||||
@wraps(fn)
|
||||
def wrapper(*args, **kwargs):
|
||||
result = fn(*args, **kwargs)
|
||||
celery_worker.reload()
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
celery_app.task = wrap_task(celery_app.task)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def instrument(tracer_provider, memory_exporter):
|
||||
CeleryInstrumentor().instrument(tracer_provider=tracer_provider)
|
||||
memory_exporter.clear()
|
||||
|
||||
yield
|
||||
|
||||
CeleryInstrumentor().uninstrument()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def tracer_provider(memory_exporter):
|
||||
original_tracer_provider = trace_api.get_tracer_provider()
|
||||
|
||||
tracer_provider = TracerProvider()
|
||||
|
||||
span_processor = export.SimpleExportSpanProcessor(memory_exporter)
|
||||
tracer_provider.add_span_processor(span_processor)
|
||||
|
||||
trace_api.set_tracer_provider(tracer_provider)
|
||||
|
||||
yield tracer_provider
|
||||
|
||||
trace_api.set_tracer_provider(original_tracer_provider)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def memory_exporter():
|
||||
memory_exporter = InMemorySpanExporter()
|
||||
return memory_exporter
|
@ -0,0 +1,532 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import time
|
||||
|
||||
import celery
|
||||
import pytest
|
||||
from celery import signals
|
||||
from celery.exceptions import Retry
|
||||
|
||||
import opentelemetry.instrumentation.celery
|
||||
from opentelemetry import trace as trace_api
|
||||
from opentelemetry.instrumentation.celery import CeleryInstrumentor
|
||||
from opentelemetry.sdk import resources
|
||||
from opentelemetry.sdk.trace import TracerProvider, export
|
||||
from opentelemetry.trace.status import StatusCanonicalCode
|
||||
|
||||
# set a high timeout for async executions due to issues in CI
|
||||
ASYNC_GET_TIMEOUT = 120
|
||||
|
||||
|
||||
class MyException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="inconsistent test results")
|
||||
def test_instrumentation_info(celery_app, memory_exporter):
|
||||
@celery_app.task
|
||||
def fn_task():
|
||||
return 42
|
||||
|
||||
result = fn_task.apply_async()
|
||||
assert result.get(timeout=ASYNC_GET_TIMEOUT) == 42
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 2
|
||||
|
||||
async_span, run_span = spans
|
||||
|
||||
assert (
|
||||
async_span.instrumentation_info.name
|
||||
== opentelemetry.instrumentation.celery.__name__
|
||||
)
|
||||
assert (
|
||||
async_span.instrumentation_info.version
|
||||
== opentelemetry.instrumentation.celery.__version__
|
||||
)
|
||||
assert (
|
||||
run_span.instrumentation_info.name
|
||||
== opentelemetry.instrumentation.celery.__name__
|
||||
)
|
||||
assert (
|
||||
run_span.instrumentation_info.version
|
||||
== opentelemetry.instrumentation.celery.__version__
|
||||
)
|
||||
|
||||
|
||||
def test_fn_task_run(celery_app, memory_exporter):
|
||||
@celery_app.task
|
||||
def fn_task():
|
||||
return 42
|
||||
|
||||
t = fn_task.run()
|
||||
assert t == 42
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 0
|
||||
|
||||
|
||||
def test_fn_task_call(celery_app, memory_exporter):
|
||||
@celery_app.task
|
||||
def fn_task():
|
||||
return 42
|
||||
|
||||
t = fn_task()
|
||||
assert t == 42
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 0
|
||||
|
||||
|
||||
def test_fn_task_apply(celery_app, memory_exporter):
|
||||
@celery_app.task
|
||||
def fn_task():
|
||||
return 42
|
||||
|
||||
t = fn_task.apply()
|
||||
assert t.successful() is True
|
||||
assert t.result == 42
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 1
|
||||
|
||||
span = spans[0]
|
||||
|
||||
assert span.status.is_ok is True
|
||||
assert span.name == "test_celery_functional.fn_task"
|
||||
assert span.attributes.get("messaging.message_id") == t.task_id
|
||||
assert (
|
||||
span.attributes.get("celery.task_name")
|
||||
== "test_celery_functional.fn_task"
|
||||
)
|
||||
assert span.attributes.get("celery.action") == "run"
|
||||
assert span.attributes.get("celery.state") == "SUCCESS"
|
||||
|
||||
|
||||
def test_fn_task_apply_bind(celery_app, memory_exporter):
|
||||
@celery_app.task(bind=True)
|
||||
def fn_task(self):
|
||||
return self
|
||||
|
||||
t = fn_task.apply()
|
||||
assert t.successful() is True
|
||||
assert "fn_task" in t.result.name
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 1
|
||||
|
||||
span = spans[0]
|
||||
|
||||
assert span.status.is_ok is True
|
||||
assert span.name == "test_celery_functional.fn_task"
|
||||
assert span.attributes.get("messaging.message_id") == t.task_id
|
||||
assert (
|
||||
span.attributes.get("celery.task_name")
|
||||
== "test_celery_functional.fn_task"
|
||||
)
|
||||
assert span.attributes.get("celery.action") == "run"
|
||||
assert span.attributes.get("celery.state") == "SUCCESS"
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="inconsistent test results")
|
||||
def test_fn_task_apply_async(celery_app, memory_exporter):
|
||||
@celery_app.task
|
||||
def fn_task_parameters(user, force_logout=False):
|
||||
return (user, force_logout)
|
||||
|
||||
result = fn_task_parameters.apply_async(
|
||||
args=["user"], kwargs={"force_logout": True}
|
||||
)
|
||||
assert result.get(timeout=ASYNC_GET_TIMEOUT) == ["user", True]
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 2
|
||||
|
||||
async_span, run_span = spans
|
||||
|
||||
assert run_span.context.trace_id != async_span.context.trace_id
|
||||
|
||||
assert async_span.status.is_ok is True
|
||||
assert async_span.name == "test_celery_functional.fn_task_parameters"
|
||||
assert async_span.attributes.get("celery.action") == "apply_async"
|
||||
assert async_span.attributes.get("messaging.message_id") == result.task_id
|
||||
assert (
|
||||
async_span.attributes.get("celery.task_name")
|
||||
== "test_celery_functional.fn_task_parameters"
|
||||
)
|
||||
|
||||
assert run_span.status.is_ok is True
|
||||
assert run_span.name == "test_celery_functional.fn_task_parameters"
|
||||
assert run_span.attributes.get("celery.action") == "run"
|
||||
assert run_span.attributes.get("celery.state") == "SUCCESS"
|
||||
assert run_span.attributes.get("messaging.message_id") == result.task_id
|
||||
assert (
|
||||
run_span.attributes.get("celery.task_name")
|
||||
== "test_celery_functional.fn_task_parameters"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="inconsistent test results")
|
||||
def test_concurrent_delays(celery_app, memory_exporter):
|
||||
@celery_app.task
|
||||
def fn_task():
|
||||
return 42
|
||||
|
||||
results = [fn_task.delay() for _ in range(100)]
|
||||
|
||||
for result in results:
|
||||
assert result.get(timeout=ASYNC_GET_TIMEOUT) == 42
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
|
||||
assert len(spans) == 200
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="inconsistent test results")
|
||||
def test_fn_task_delay(celery_app, memory_exporter):
|
||||
@celery_app.task
|
||||
def fn_task_parameters(user, force_logout=False):
|
||||
return (user, force_logout)
|
||||
|
||||
result = fn_task_parameters.delay("user", force_logout=True)
|
||||
assert result.get(timeout=ASYNC_GET_TIMEOUT) == ["user", True]
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 2
|
||||
|
||||
async_span, run_span = spans
|
||||
|
||||
assert run_span.context.trace_id != async_span.context.trace_id
|
||||
|
||||
assert async_span.status.is_ok is True
|
||||
assert async_span.name == "test_celery_functional.fn_task_parameters"
|
||||
assert async_span.attributes.get("celery.action") == "apply_async"
|
||||
assert async_span.attributes.get("messaging.message_id") == result.task_id
|
||||
assert (
|
||||
async_span.attributes.get("celery.task_name")
|
||||
== "test_celery_functional.fn_task_parameters"
|
||||
)
|
||||
|
||||
assert run_span.status.is_ok is True
|
||||
assert run_span.name == "test_celery_functional.fn_task_parameters"
|
||||
assert run_span.attributes.get("celery.action") == "run"
|
||||
assert run_span.attributes.get("celery.state") == "SUCCESS"
|
||||
assert run_span.attributes.get("messaging.message_id") == result.task_id
|
||||
assert (
|
||||
run_span.attributes.get("celery.task_name")
|
||||
== "test_celery_functional.fn_task_parameters"
|
||||
)
|
||||
|
||||
|
||||
def test_fn_exception(celery_app, memory_exporter):
|
||||
@celery_app.task
|
||||
def fn_exception():
|
||||
raise Exception("Task class is failing")
|
||||
|
||||
result = fn_exception.apply()
|
||||
|
||||
assert result.failed() is True
|
||||
assert "Task class is failing" in result.traceback
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 1
|
||||
|
||||
span = spans[0]
|
||||
|
||||
assert span.status.is_ok is False
|
||||
assert span.name == "test_celery_functional.fn_exception"
|
||||
assert span.attributes.get("celery.action") == "run"
|
||||
assert span.attributes.get("celery.state") == "FAILURE"
|
||||
assert (
|
||||
span.attributes.get("celery.task_name")
|
||||
== "test_celery_functional.fn_exception"
|
||||
)
|
||||
assert span.status.canonical_code == StatusCanonicalCode.UNKNOWN
|
||||
assert span.attributes.get("messaging.message_id") == result.task_id
|
||||
assert "Task class is failing" in span.status.description
|
||||
|
||||
|
||||
def test_fn_exception_expected(celery_app, memory_exporter):
|
||||
@celery_app.task(throws=(MyException,))
|
||||
def fn_exception():
|
||||
raise MyException("Task class is failing")
|
||||
|
||||
result = fn_exception.apply()
|
||||
|
||||
assert result.failed() is True
|
||||
assert "Task class is failing" in result.traceback
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 1
|
||||
|
||||
span = spans[0]
|
||||
|
||||
assert span.status.is_ok is True
|
||||
assert span.status.canonical_code == StatusCanonicalCode.OK
|
||||
assert span.name == "test_celery_functional.fn_exception"
|
||||
assert span.attributes.get("celery.action") == "run"
|
||||
assert span.attributes.get("celery.state") == "FAILURE"
|
||||
assert (
|
||||
span.attributes.get("celery.task_name")
|
||||
== "test_celery_functional.fn_exception"
|
||||
)
|
||||
assert span.attributes.get("messaging.message_id") == result.task_id
|
||||
|
||||
|
||||
def test_fn_retry_exception(celery_app, memory_exporter):
|
||||
@celery_app.task
|
||||
def fn_exception():
|
||||
raise Retry("Task class is being retried")
|
||||
|
||||
result = fn_exception.apply()
|
||||
|
||||
assert result.failed() is False
|
||||
assert "Task class is being retried" in result.traceback
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 1
|
||||
|
||||
span = spans[0]
|
||||
|
||||
assert span.status.is_ok is True
|
||||
assert span.status.canonical_code == StatusCanonicalCode.OK
|
||||
assert span.name == "test_celery_functional.fn_exception"
|
||||
assert span.attributes.get("celery.action") == "run"
|
||||
assert span.attributes.get("celery.state") == "RETRY"
|
||||
assert (
|
||||
span.attributes.get("celery.task_name")
|
||||
== "test_celery_functional.fn_exception"
|
||||
)
|
||||
assert span.attributes.get("messaging.message_id") == result.task_id
|
||||
|
||||
|
||||
def test_class_task(celery_app, memory_exporter):
|
||||
class BaseTask(celery_app.Task):
|
||||
def run(self):
|
||||
return 42
|
||||
|
||||
task = BaseTask()
|
||||
# register the Task class if it's available (required in Celery 4.0+)
|
||||
register_task = getattr(celery_app, "register_task", None)
|
||||
if register_task is not None:
|
||||
register_task(task)
|
||||
|
||||
result = task.apply()
|
||||
|
||||
assert result.successful() is True
|
||||
assert result.result == 42
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 1
|
||||
|
||||
span = spans[0]
|
||||
|
||||
assert span.status.is_ok is True
|
||||
assert span.name == "test_celery_functional.BaseTask"
|
||||
assert (
|
||||
span.attributes.get("celery.task_name")
|
||||
== "test_celery_functional.BaseTask"
|
||||
)
|
||||
assert span.attributes.get("celery.action") == "run"
|
||||
assert span.attributes.get("celery.state") == "SUCCESS"
|
||||
assert span.attributes.get("messaging.message_id") == result.task_id
|
||||
|
||||
|
||||
def test_class_task_exception(celery_app, memory_exporter):
|
||||
class BaseTask(celery_app.Task):
|
||||
def run(self):
|
||||
raise Exception("Task class is failing")
|
||||
|
||||
task = BaseTask()
|
||||
# register the Task class if it's available (required in Celery 4.0+)
|
||||
register_task = getattr(celery_app, "register_task", None)
|
||||
if register_task is not None:
|
||||
register_task(task)
|
||||
|
||||
result = task.apply()
|
||||
|
||||
assert result.failed() is True
|
||||
assert "Task class is failing" in result.traceback
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 1
|
||||
|
||||
span = spans[0]
|
||||
|
||||
assert span.status.is_ok is False
|
||||
assert span.name == "test_celery_functional.BaseTask"
|
||||
assert (
|
||||
span.attributes.get("celery.task_name")
|
||||
== "test_celery_functional.BaseTask"
|
||||
)
|
||||
assert span.attributes.get("celery.action") == "run"
|
||||
assert span.attributes.get("celery.state") == "FAILURE"
|
||||
assert span.status.canonical_code == StatusCanonicalCode.UNKNOWN
|
||||
assert span.attributes.get("messaging.message_id") == result.task_id
|
||||
assert "Task class is failing" in span.status.description
|
||||
|
||||
|
||||
def test_class_task_exception_excepted(celery_app, memory_exporter):
|
||||
class BaseTask(celery_app.Task):
|
||||
throws = (MyException,)
|
||||
|
||||
def run(self):
|
||||
raise MyException("Task class is failing")
|
||||
|
||||
task = BaseTask()
|
||||
# register the Task class if it's available (required in Celery 4.0+)
|
||||
register_task = getattr(celery_app, "register_task", None)
|
||||
if register_task is not None:
|
||||
register_task(task)
|
||||
|
||||
result = task.apply()
|
||||
|
||||
assert result.failed() is True
|
||||
assert "Task class is failing" in result.traceback
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 1
|
||||
|
||||
span = spans[0]
|
||||
|
||||
assert span.status.is_ok is True
|
||||
assert span.status.canonical_code == StatusCanonicalCode.OK
|
||||
assert span.name == "test_celery_functional.BaseTask"
|
||||
assert span.attributes.get("celery.action") == "run"
|
||||
assert span.attributes.get("celery.state") == "FAILURE"
|
||||
assert span.attributes.get("messaging.message_id") == result.task_id
|
||||
|
||||
|
||||
def test_shared_task(celery_app, memory_exporter):
|
||||
"""Ensure Django Shared Task are supported"""
|
||||
|
||||
@celery.shared_task
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
result = add.apply([2, 2])
|
||||
assert result.result == 4
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 1
|
||||
|
||||
span = spans[0]
|
||||
|
||||
assert span.status.is_ok is True
|
||||
assert span.name == "test_celery_functional.add"
|
||||
assert (
|
||||
span.attributes.get("celery.task_name") == "test_celery_functional.add"
|
||||
)
|
||||
assert span.attributes.get("celery.action") == "run"
|
||||
assert span.attributes.get("celery.state") == "SUCCESS"
|
||||
assert span.attributes.get("messaging.message_id") == result.task_id
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="inconsistent test results")
|
||||
def test_apply_async_previous_style_tasks(
|
||||
celery_app, celery_worker, memory_exporter
|
||||
):
|
||||
"""Ensures apply_async is properly patched if Celery 1.0 style tasks are
|
||||
used even in newer versions. This should extend support to previous versions
|
||||
of Celery."""
|
||||
|
||||
class CelerySuperClass(celery.task.Task):
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def apply_async(cls, args=None, kwargs=None, **kwargs_):
|
||||
return super(CelerySuperClass, cls).apply_async(
|
||||
args=args, kwargs=kwargs, **kwargs_
|
||||
)
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
if "stop" in kwargs:
|
||||
# avoid call loop
|
||||
return
|
||||
CelerySubClass.apply_async(args=[], kwargs={"stop": True}).get(
|
||||
timeout=ASYNC_GET_TIMEOUT
|
||||
)
|
||||
|
||||
class CelerySubClass(CelerySuperClass):
|
||||
pass
|
||||
|
||||
celery_worker.reload()
|
||||
|
||||
task = CelerySubClass()
|
||||
result = task.apply()
|
||||
|
||||
spans = memory_exporter.get_finished_spans()
|
||||
assert len(spans) == 3
|
||||
|
||||
async_span, async_run_span, run_span = spans
|
||||
|
||||
assert run_span.status.is_ok is True
|
||||
assert run_span.name == "test_celery_functional.CelerySubClass"
|
||||
assert (
|
||||
run_span.attributes.get("celery.task_name")
|
||||
== "test_celery_functional.CelerySubClass"
|
||||
)
|
||||
assert run_span.attributes.get("celery.action") == "run"
|
||||
assert run_span.attributes.get("celery.state") == "SUCCESS"
|
||||
assert run_span.attributes.get("messaging.message_id") == result.task_id
|
||||
|
||||
assert async_run_span.status.is_ok is True
|
||||
assert async_run_span.name == "test_celery_functional.CelerySubClass"
|
||||
assert (
|
||||
async_run_span.attributes.get("celery.task_name")
|
||||
== "test_celery_functional.CelerySubClass"
|
||||
)
|
||||
assert async_run_span.attributes.get("celery.action") == "run"
|
||||
assert async_run_span.attributes.get("celery.state") == "SUCCESS"
|
||||
assert (
|
||||
async_run_span.attributes.get("messaging.message_id") != result.task_id
|
||||
)
|
||||
|
||||
assert async_span.status.is_ok is True
|
||||
assert async_span.name == "test_celery_functional.CelerySubClass"
|
||||
assert (
|
||||
async_span.attributes.get("celery.task_name")
|
||||
== "test_celery_functional.CelerySubClass"
|
||||
)
|
||||
assert async_span.attributes.get("celery.action") == "apply_async"
|
||||
assert async_span.attributes.get("messaging.message_id") != result.task_id
|
||||
assert async_span.attributes.get(
|
||||
"messaging.message_id"
|
||||
) == async_run_span.attributes.get("messaging.message_id")
|
||||
|
||||
|
||||
def test_custom_tracer_provider(celery_app, memory_exporter):
|
||||
@celery_app.task
|
||||
def fn_task():
|
||||
return 42
|
||||
|
||||
resource = resources.Resource.create({})
|
||||
tracer_provider = TracerProvider(resource=resource)
|
||||
span_processor = export.SimpleExportSpanProcessor(memory_exporter)
|
||||
tracer_provider.add_span_processor(span_processor)
|
||||
|
||||
trace_api.set_tracer_provider(tracer_provider)
|
||||
|
||||
CeleryInstrumentor().uninstrument()
|
||||
CeleryInstrumentor().instrument(tracer_provider=tracer_provider)
|
||||
|
||||
fn_task.delay()
|
||||
|
||||
spans_list = memory_exporter.get_finished_spans()
|
||||
assert len(spans_list) == 1
|
||||
|
||||
span = spans_list[0]
|
||||
assert span.resource == resource
|
115
tests/opentelemetry-docker-tests/tests/check_availability.py
Normal file
115
tests/opentelemetry-docker-tests/tests/check_availability.py
Normal file
@ -0,0 +1,115 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
|
||||
import mysql.connector
|
||||
import psycopg2
|
||||
import pymongo
|
||||
import redis
|
||||
|
||||
MONGODB_COLLECTION_NAME = "test"
|
||||
MONGODB_DB_NAME = os.getenv("MONGODB_DB_NAME", "opentelemetry-tests")
|
||||
MONGODB_HOST = os.getenv("MONGODB_HOST", "localhost")
|
||||
MONGODB_PORT = int(os.getenv("MONGODB_PORT", "27017"))
|
||||
MYSQL_DB_NAME = os.getenv("MYSQL_DB_NAME ", "opentelemetry-tests")
|
||||
MYSQL_HOST = os.getenv("MYSQL_HOST ", "localhost")
|
||||
MYSQL_PORT = int(os.getenv("MYSQL_PORT ", "3306"))
|
||||
MYSQL_USER = os.getenv("MYSQL_USER ", "testuser")
|
||||
MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD ", "testpassword")
|
||||
POSTGRES_DB_NAME = os.getenv("POSTGRESQL_DB_NAME", "opentelemetry-tests")
|
||||
POSTGRES_HOST = os.getenv("POSTGRESQL_HOST", "localhost")
|
||||
POSTGRES_PASSWORD = os.getenv("POSTGRESQL_HOST", "testpassword")
|
||||
POSTGRES_PORT = int(os.getenv("POSTGRESQL_PORT", "5432"))
|
||||
POSTGRES_USER = os.getenv("POSTGRESQL_HOST", "testuser")
|
||||
REDIS_HOST = os.getenv("REDIS_HOST", "localhost")
|
||||
REDIS_PORT = int(os.getenv("REDIS_PORT ", "6379"))
|
||||
RETRY_COUNT = 5
|
||||
RETRY_INTERVAL = 5 # Seconds
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def retryable(func):
|
||||
def wrapper():
|
||||
# Try to connect to DB
|
||||
for i in range(RETRY_COUNT):
|
||||
try:
|
||||
func()
|
||||
return
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
logger.error(
|
||||
"waiting for %s, retry %d/%d [%s]",
|
||||
func.__name__,
|
||||
i + 1,
|
||||
RETRY_COUNT,
|
||||
ex,
|
||||
)
|
||||
time.sleep(RETRY_INTERVAL)
|
||||
raise Exception("waiting for {} failed".format(func.__name__))
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@retryable
|
||||
def check_pymongo_connection():
|
||||
client = pymongo.MongoClient(
|
||||
MONGODB_HOST, MONGODB_PORT, serverSelectionTimeoutMS=2000
|
||||
)
|
||||
db = client[MONGODB_DB_NAME]
|
||||
collection = db[MONGODB_COLLECTION_NAME]
|
||||
collection.find_one()
|
||||
client.close()
|
||||
|
||||
|
||||
@retryable
|
||||
def check_mysql_connection():
|
||||
connection = mysql.connector.connect(
|
||||
user=MYSQL_USER,
|
||||
password=MYSQL_PASSWORD,
|
||||
host=MYSQL_HOST,
|
||||
port=MYSQL_PORT,
|
||||
database=MYSQL_DB_NAME,
|
||||
)
|
||||
connection.close()
|
||||
|
||||
|
||||
@retryable
|
||||
def check_postgres_connection():
|
||||
connection = psycopg2.connect(
|
||||
dbname=POSTGRES_DB_NAME,
|
||||
user=POSTGRES_USER,
|
||||
password=POSTGRES_PASSWORD,
|
||||
host=POSTGRES_HOST,
|
||||
port=POSTGRES_PORT,
|
||||
)
|
||||
connection.close()
|
||||
|
||||
|
||||
@retryable
|
||||
def check_redis_connection():
|
||||
connection = redis.Redis(host=REDIS_HOST, port=REDIS_PORT)
|
||||
connection.hgetall("*")
|
||||
|
||||
|
||||
def check_docker_services_availability():
|
||||
# Check if Docker services accept connections
|
||||
check_pymongo_connection()
|
||||
check_mysql_connection()
|
||||
check_postgres_connection()
|
||||
check_redis_connection()
|
||||
|
||||
|
||||
check_docker_services_availability()
|
47
tests/opentelemetry-docker-tests/tests/docker-compose.yml
Normal file
47
tests/opentelemetry-docker-tests/tests/docker-compose.yml
Normal file
@ -0,0 +1,47 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
otmongo:
|
||||
ports:
|
||||
- "27017:27017"
|
||||
image: mongo:latest
|
||||
otmysql:
|
||||
ports:
|
||||
- "3306:3306"
|
||||
image: mysql:latest
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_USER: testuser
|
||||
MYSQL_PASSWORD: testpassword
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
|
||||
MYSQL_DATABASE: opentelemetry-tests
|
||||
otpostgres:
|
||||
image: postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_USER: testuser
|
||||
POSTGRES_PASSWORD: testpassword
|
||||
POSTGRES_DB: opentelemetry-tests
|
||||
otredis:
|
||||
image: redis:4.0-alpine
|
||||
ports:
|
||||
- "127.0.0.1:6379:6379"
|
||||
otjaeger:
|
||||
image: jaegertracing/all-in-one:1.8
|
||||
environment:
|
||||
COLLECTOR_ZIPKIN_HTTP_PORT: "9411"
|
||||
ports:
|
||||
- "5775:5775/udp"
|
||||
- "6831:6831/udp"
|
||||
- "6832:6832/udp"
|
||||
- "5778:5778"
|
||||
- "16686:16686"
|
||||
- "14268:14268"
|
||||
- "9411:9411"
|
||||
otopencensus:
|
||||
image: omnition/opencensus-collector:0.1.11
|
||||
command: --logging-exporter DEBUG
|
||||
ports:
|
||||
- "8888:8888"
|
||||
- "55678:55678"
|
@ -0,0 +1,98 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
import mysql.connector
|
||||
|
||||
from opentelemetry import trace as trace_api
|
||||
from opentelemetry.instrumentation.mysql import MySQLInstrumentor
|
||||
from opentelemetry.test.test_base import TestBase
|
||||
|
||||
MYSQL_USER = os.getenv("MYSQL_USER ", "testuser")
|
||||
MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD ", "testpassword")
|
||||
MYSQL_HOST = os.getenv("MYSQL_HOST ", "localhost")
|
||||
MYSQL_PORT = int(os.getenv("MYSQL_PORT ", "3306"))
|
||||
MYSQL_DB_NAME = os.getenv("MYSQL_DB_NAME ", "opentelemetry-tests")
|
||||
|
||||
|
||||
class TestFunctionalMysql(TestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._connection = None
|
||||
cls._cursor = None
|
||||
cls._tracer = cls.tracer_provider.get_tracer(__name__)
|
||||
MySQLInstrumentor().instrument()
|
||||
cls._connection = mysql.connector.connect(
|
||||
user=MYSQL_USER,
|
||||
password=MYSQL_PASSWORD,
|
||||
host=MYSQL_HOST,
|
||||
port=MYSQL_PORT,
|
||||
database=MYSQL_DB_NAME,
|
||||
)
|
||||
cls._cursor = cls._connection.cursor()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
if cls._connection:
|
||||
cls._connection.close()
|
||||
MySQLInstrumentor().uninstrument()
|
||||
|
||||
def validate_spans(self):
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 2)
|
||||
for span in spans:
|
||||
if span.name == "rootSpan":
|
||||
root_span = span
|
||||
else:
|
||||
db_span = span
|
||||
self.assertIsInstance(span.start_time, int)
|
||||
self.assertIsInstance(span.end_time, int)
|
||||
self.assertIsNotNone(root_span)
|
||||
self.assertIsNotNone(db_span)
|
||||
self.assertEqual(root_span.name, "rootSpan")
|
||||
self.assertEqual(db_span.name, "mysql.opentelemetry-tests")
|
||||
self.assertIsNotNone(db_span.parent)
|
||||
self.assertIs(db_span.parent, root_span.get_context())
|
||||
self.assertIs(db_span.kind, trace_api.SpanKind.CLIENT)
|
||||
self.assertEqual(db_span.attributes["db.instance"], MYSQL_DB_NAME)
|
||||
self.assertEqual(db_span.attributes["net.peer.name"], MYSQL_HOST)
|
||||
self.assertEqual(db_span.attributes["net.peer.port"], MYSQL_PORT)
|
||||
|
||||
def test_execute(self):
|
||||
"""Should create a child span for execute
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"):
|
||||
self._cursor.execute("CREATE TABLE IF NOT EXISTS test (id INT)")
|
||||
self.validate_spans()
|
||||
|
||||
def test_executemany(self):
|
||||
"""Should create a child span for executemany
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"):
|
||||
data = (("1",), ("2",), ("3",))
|
||||
stmt = "INSERT INTO test (id) VALUES (%s)"
|
||||
self._cursor.executemany(stmt, data)
|
||||
self.validate_spans()
|
||||
|
||||
def test_callproc(self):
|
||||
"""Should create a child span for callproc
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"), self.assertRaises(
|
||||
Exception
|
||||
):
|
||||
self._cursor.callproc("test", ())
|
||||
self.validate_spans()
|
@ -0,0 +1,60 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.context import attach, detach, set_value
|
||||
from opentelemetry.exporter.opencensus.trace_exporter import (
|
||||
OpenCensusSpanExporter,
|
||||
)
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor
|
||||
from opentelemetry.test.test_base import TestBase
|
||||
|
||||
|
||||
class ExportStatusSpanProcessor(SimpleExportSpanProcessor):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.export_status = []
|
||||
|
||||
def on_end(self, span):
|
||||
token = attach(set_value("suppress_instrumentation", True))
|
||||
self.export_status.append(self.span_exporter.export((span,)))
|
||||
detach(token)
|
||||
|
||||
|
||||
class TestOpenCensusSpanExporter(TestBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
trace.set_tracer_provider(TracerProvider())
|
||||
self.tracer = trace.get_tracer(__name__)
|
||||
self.span_processor = ExportStatusSpanProcessor(
|
||||
OpenCensusSpanExporter(
|
||||
service_name="basic-service", endpoint="localhost:55678"
|
||||
)
|
||||
)
|
||||
|
||||
trace.get_tracer_provider().add_span_processor(self.span_processor)
|
||||
|
||||
def test_export(self):
|
||||
with self.tracer.start_as_current_span("foo"):
|
||||
with self.tracer.start_as_current_span("bar"):
|
||||
with self.tracer.start_as_current_span("baz"):
|
||||
pass
|
||||
|
||||
self.assertTrue(len(self.span_processor.export_status), 3)
|
||||
|
||||
for export_status in self.span_processor.export_status:
|
||||
self.assertEqual(export_status.name, "SUCCESS")
|
||||
self.assertEqual(export_status.value, 0)
|
@ -0,0 +1,200 @@
|
||||
# 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 asyncio
|
||||
import os
|
||||
import time
|
||||
|
||||
import aiopg
|
||||
import psycopg2
|
||||
import pytest
|
||||
|
||||
from opentelemetry import trace as trace_api
|
||||
from opentelemetry.instrumentation.aiopg import AiopgInstrumentor
|
||||
from opentelemetry.test.test_base import TestBase
|
||||
|
||||
POSTGRES_HOST = os.getenv("POSTGRESQL_HOST ", "localhost")
|
||||
POSTGRES_PORT = int(os.getenv("POSTGRESQL_PORT ", "5432"))
|
||||
POSTGRES_DB_NAME = os.getenv("POSTGRESQL_DB_NAME ", "opentelemetry-tests")
|
||||
POSTGRES_PASSWORD = os.getenv("POSTGRESQL_HOST ", "testpassword")
|
||||
POSTGRES_USER = os.getenv("POSTGRESQL_HOST ", "testuser")
|
||||
|
||||
|
||||
def async_call(coro):
|
||||
loop = asyncio.get_event_loop()
|
||||
return loop.run_until_complete(coro)
|
||||
|
||||
|
||||
class TestFunctionalAiopgConnect(TestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._connection = None
|
||||
cls._cursor = None
|
||||
cls._tracer = cls.tracer_provider.get_tracer(__name__)
|
||||
AiopgInstrumentor().instrument(tracer_provider=cls.tracer_provider)
|
||||
cls._connection = async_call(
|
||||
aiopg.connect(
|
||||
dbname=POSTGRES_DB_NAME,
|
||||
user=POSTGRES_USER,
|
||||
password=POSTGRES_PASSWORD,
|
||||
host=POSTGRES_HOST,
|
||||
port=POSTGRES_PORT,
|
||||
)
|
||||
)
|
||||
cls._cursor = async_call(cls._connection.cursor())
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
if cls._cursor:
|
||||
cls._cursor.close()
|
||||
if cls._connection:
|
||||
cls._connection.close()
|
||||
AiopgInstrumentor().uninstrument()
|
||||
|
||||
def validate_spans(self):
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 2)
|
||||
for span in spans:
|
||||
if span.name == "rootSpan":
|
||||
root_span = span
|
||||
else:
|
||||
child_span = span
|
||||
self.assertIsInstance(span.start_time, int)
|
||||
self.assertIsInstance(span.end_time, int)
|
||||
self.assertIsNotNone(root_span)
|
||||
self.assertIsNotNone(child_span)
|
||||
self.assertEqual(root_span.name, "rootSpan")
|
||||
self.assertEqual(child_span.name, "postgresql.opentelemetry-tests")
|
||||
self.assertIsNotNone(child_span.parent)
|
||||
self.assertIs(child_span.parent, root_span.get_context())
|
||||
self.assertIs(child_span.kind, trace_api.SpanKind.CLIENT)
|
||||
self.assertEqual(
|
||||
child_span.attributes["db.instance"], POSTGRES_DB_NAME
|
||||
)
|
||||
self.assertEqual(child_span.attributes["net.peer.name"], POSTGRES_HOST)
|
||||
self.assertEqual(child_span.attributes["net.peer.port"], POSTGRES_PORT)
|
||||
|
||||
def test_execute(self):
|
||||
"""Should create a child span for execute method
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"):
|
||||
async_call(
|
||||
self._cursor.execute(
|
||||
"CREATE TABLE IF NOT EXISTS test (id integer)"
|
||||
)
|
||||
)
|
||||
self.validate_spans()
|
||||
|
||||
def test_executemany(self):
|
||||
"""Should create a child span for executemany
|
||||
"""
|
||||
with pytest.raises(psycopg2.ProgrammingError):
|
||||
with self._tracer.start_as_current_span("rootSpan"):
|
||||
data = (("1",), ("2",), ("3",))
|
||||
stmt = "INSERT INTO test (id) VALUES (%s)"
|
||||
async_call(self._cursor.executemany(stmt, data))
|
||||
self.validate_spans()
|
||||
|
||||
def test_callproc(self):
|
||||
"""Should create a child span for callproc
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"), self.assertRaises(
|
||||
Exception
|
||||
):
|
||||
async_call(self._cursor.callproc("test", ()))
|
||||
self.validate_spans()
|
||||
|
||||
|
||||
class TestFunctionalAiopgCreatePool(TestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._connection = None
|
||||
cls._cursor = None
|
||||
cls._tracer = cls.tracer_provider.get_tracer(__name__)
|
||||
AiopgInstrumentor().instrument(tracer_provider=cls.tracer_provider)
|
||||
cls._pool = async_call(
|
||||
aiopg.create_pool(
|
||||
dbname=POSTGRES_DB_NAME,
|
||||
user=POSTGRES_USER,
|
||||
password=POSTGRES_PASSWORD,
|
||||
host=POSTGRES_HOST,
|
||||
port=POSTGRES_PORT,
|
||||
)
|
||||
)
|
||||
cls._connection = async_call(cls._pool.acquire())
|
||||
cls._cursor = async_call(cls._connection.cursor())
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
if cls._cursor:
|
||||
cls._cursor.close()
|
||||
if cls._connection:
|
||||
cls._connection.close()
|
||||
if cls._pool:
|
||||
cls._pool.close()
|
||||
AiopgInstrumentor().uninstrument()
|
||||
|
||||
def validate_spans(self):
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 2)
|
||||
for span in spans:
|
||||
if span.name == "rootSpan":
|
||||
root_span = span
|
||||
else:
|
||||
child_span = span
|
||||
self.assertIsInstance(span.start_time, int)
|
||||
self.assertIsInstance(span.end_time, int)
|
||||
self.assertIsNotNone(root_span)
|
||||
self.assertIsNotNone(child_span)
|
||||
self.assertEqual(root_span.name, "rootSpan")
|
||||
self.assertEqual(child_span.name, "postgresql.opentelemetry-tests")
|
||||
self.assertIsNotNone(child_span.parent)
|
||||
self.assertIs(child_span.parent, root_span.get_context())
|
||||
self.assertIs(child_span.kind, trace_api.SpanKind.CLIENT)
|
||||
self.assertEqual(
|
||||
child_span.attributes["db.instance"], POSTGRES_DB_NAME
|
||||
)
|
||||
self.assertEqual(child_span.attributes["net.peer.name"], POSTGRES_HOST)
|
||||
self.assertEqual(child_span.attributes["net.peer.port"], POSTGRES_PORT)
|
||||
|
||||
def test_execute(self):
|
||||
"""Should create a child span for execute method
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"):
|
||||
async_call(
|
||||
self._cursor.execute(
|
||||
"CREATE TABLE IF NOT EXISTS test (id integer)"
|
||||
)
|
||||
)
|
||||
self.validate_spans()
|
||||
|
||||
def test_executemany(self):
|
||||
"""Should create a child span for executemany
|
||||
"""
|
||||
with pytest.raises(psycopg2.ProgrammingError):
|
||||
with self._tracer.start_as_current_span("rootSpan"):
|
||||
data = (("1",), ("2",), ("3",))
|
||||
stmt = "INSERT INTO test (id) VALUES (%s)"
|
||||
async_call(self._cursor.executemany(stmt, data))
|
||||
self.validate_spans()
|
||||
|
||||
def test_callproc(self):
|
||||
"""Should create a child span for callproc
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"), self.assertRaises(
|
||||
Exception
|
||||
):
|
||||
async_call(self._cursor.callproc("test", ()))
|
||||
self.validate_spans()
|
@ -0,0 +1,105 @@
|
||||
# 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 time
|
||||
|
||||
import psycopg2
|
||||
|
||||
from opentelemetry import trace as trace_api
|
||||
from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor
|
||||
from opentelemetry.test.test_base import TestBase
|
||||
|
||||
POSTGRES_HOST = os.getenv("POSTGRESQL_HOST ", "localhost")
|
||||
POSTGRES_PORT = int(os.getenv("POSTGRESQL_PORT ", "5432"))
|
||||
POSTGRES_DB_NAME = os.getenv("POSTGRESQL_DB_NAME ", "opentelemetry-tests")
|
||||
POSTGRES_PASSWORD = os.getenv("POSTGRESQL_HOST ", "testpassword")
|
||||
POSTGRES_USER = os.getenv("POSTGRESQL_HOST ", "testuser")
|
||||
|
||||
|
||||
class TestFunctionalPsycopg(TestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._connection = None
|
||||
cls._cursor = None
|
||||
cls._tracer = cls.tracer_provider.get_tracer(__name__)
|
||||
Psycopg2Instrumentor().instrument(tracer_provider=cls.tracer_provider)
|
||||
cls._connection = psycopg2.connect(
|
||||
dbname=POSTGRES_DB_NAME,
|
||||
user=POSTGRES_USER,
|
||||
password=POSTGRES_PASSWORD,
|
||||
host=POSTGRES_HOST,
|
||||
port=POSTGRES_PORT,
|
||||
)
|
||||
cls._connection.set_session(autocommit=True)
|
||||
cls._cursor = cls._connection.cursor()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
if cls._cursor:
|
||||
cls._cursor.close()
|
||||
if cls._connection:
|
||||
cls._connection.close()
|
||||
Psycopg2Instrumentor().uninstrument()
|
||||
|
||||
def validate_spans(self):
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 2)
|
||||
for span in spans:
|
||||
if span.name == "rootSpan":
|
||||
root_span = span
|
||||
else:
|
||||
child_span = span
|
||||
self.assertIsInstance(span.start_time, int)
|
||||
self.assertIsInstance(span.end_time, int)
|
||||
self.assertIsNotNone(root_span)
|
||||
self.assertIsNotNone(child_span)
|
||||
self.assertEqual(root_span.name, "rootSpan")
|
||||
self.assertEqual(child_span.name, "postgresql.opentelemetry-tests")
|
||||
self.assertIsNotNone(child_span.parent)
|
||||
self.assertIs(child_span.parent, root_span.get_context())
|
||||
self.assertIs(child_span.kind, trace_api.SpanKind.CLIENT)
|
||||
self.assertEqual(
|
||||
child_span.attributes["db.instance"], POSTGRES_DB_NAME
|
||||
)
|
||||
self.assertEqual(child_span.attributes["net.peer.name"], POSTGRES_HOST)
|
||||
self.assertEqual(child_span.attributes["net.peer.port"], POSTGRES_PORT)
|
||||
|
||||
def test_execute(self):
|
||||
"""Should create a child span for execute method
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"):
|
||||
self._cursor.execute(
|
||||
"CREATE TABLE IF NOT EXISTS test (id integer)"
|
||||
)
|
||||
self.validate_spans()
|
||||
|
||||
def test_executemany(self):
|
||||
"""Should create a child span for executemany
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"):
|
||||
data = (("1",), ("2",), ("3",))
|
||||
stmt = "INSERT INTO test (id) VALUES (%s)"
|
||||
self._cursor.executemany(stmt, data)
|
||||
self.validate_spans()
|
||||
|
||||
def test_callproc(self):
|
||||
"""Should create a child span for callproc
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"), self.assertRaises(
|
||||
Exception
|
||||
):
|
||||
self._cursor.callproc("test", ())
|
||||
self.validate_spans()
|
@ -0,0 +1,116 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
|
||||
from pymongo import MongoClient
|
||||
|
||||
from opentelemetry import trace as trace_api
|
||||
from opentelemetry.instrumentation.pymongo import PymongoInstrumentor
|
||||
from opentelemetry.test.test_base import TestBase
|
||||
|
||||
MONGODB_HOST = os.getenv("MONGODB_HOST ", "localhost")
|
||||
MONGODB_PORT = int(os.getenv("MONGODB_PORT ", "27017"))
|
||||
MONGODB_DB_NAME = os.getenv("MONGODB_DB_NAME ", "opentelemetry-tests")
|
||||
MONGODB_COLLECTION_NAME = "test"
|
||||
|
||||
|
||||
class TestFunctionalPymongo(TestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._tracer = cls.tracer_provider.get_tracer(__name__)
|
||||
PymongoInstrumentor().instrument()
|
||||
client = MongoClient(
|
||||
MONGODB_HOST, MONGODB_PORT, serverSelectionTimeoutMS=2000
|
||||
)
|
||||
db = client[MONGODB_DB_NAME]
|
||||
cls._collection = db[MONGODB_COLLECTION_NAME]
|
||||
|
||||
def validate_spans(self):
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 2)
|
||||
for span in spans:
|
||||
if span.name == "rootSpan":
|
||||
root_span = span
|
||||
else:
|
||||
pymongo_span = span
|
||||
self.assertIsInstance(span.start_time, int)
|
||||
self.assertIsInstance(span.end_time, int)
|
||||
self.assertIsNot(root_span, None)
|
||||
self.assertIsNot(pymongo_span, None)
|
||||
self.assertIsNotNone(pymongo_span.parent)
|
||||
self.assertIs(pymongo_span.parent, root_span.get_context())
|
||||
self.assertIs(pymongo_span.kind, trace_api.SpanKind.CLIENT)
|
||||
self.assertEqual(
|
||||
pymongo_span.attributes["db.instance"], MONGODB_DB_NAME
|
||||
)
|
||||
self.assertEqual(
|
||||
pymongo_span.attributes["net.peer.name"], MONGODB_HOST
|
||||
)
|
||||
self.assertEqual(
|
||||
pymongo_span.attributes["net.peer.port"], MONGODB_PORT
|
||||
)
|
||||
|
||||
def test_insert(self):
|
||||
"""Should create a child span for insert
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"):
|
||||
self._collection.insert_one(
|
||||
{"name": "testName", "value": "testValue"}
|
||||
)
|
||||
self.validate_spans()
|
||||
|
||||
def test_update(self):
|
||||
"""Should create a child span for update
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"):
|
||||
self._collection.update_one(
|
||||
{"name": "testName"}, {"$set": {"value": "someOtherValue"}}
|
||||
)
|
||||
self.validate_spans()
|
||||
|
||||
def test_find(self):
|
||||
"""Should create a child span for find
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"):
|
||||
self._collection.find_one()
|
||||
self.validate_spans()
|
||||
|
||||
def test_delete(self):
|
||||
"""Should create a child span for delete
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"):
|
||||
self._collection.delete_one({"name": "testName"})
|
||||
self.validate_spans()
|
||||
|
||||
def test_uninstrument(self):
|
||||
# check that integration is working
|
||||
self._collection.find_one()
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.memory_exporter.clear()
|
||||
self.assertEqual(len(spans), 1)
|
||||
|
||||
# uninstrument and check not new spans are created
|
||||
PymongoInstrumentor().uninstrument()
|
||||
self._collection.find_one()
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.memory_exporter.clear()
|
||||
self.assertEqual(len(spans), 0)
|
||||
|
||||
# re-enable and check that it works again
|
||||
PymongoInstrumentor().instrument()
|
||||
self._collection.find_one()
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
@ -0,0 +1,97 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
|
||||
import pymysql as pymy
|
||||
|
||||
from opentelemetry import trace as trace_api
|
||||
from opentelemetry.instrumentation.pymysql import PyMySQLInstrumentor
|
||||
from opentelemetry.test.test_base import TestBase
|
||||
|
||||
MYSQL_USER = os.getenv("MYSQL_USER ", "testuser")
|
||||
MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD ", "testpassword")
|
||||
MYSQL_HOST = os.getenv("MYSQL_HOST ", "localhost")
|
||||
MYSQL_PORT = int(os.getenv("MYSQL_PORT ", "3306"))
|
||||
MYSQL_DB_NAME = os.getenv("MYSQL_DB_NAME ", "opentelemetry-tests")
|
||||
|
||||
|
||||
class TestFunctionalPyMysql(TestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._connection = None
|
||||
cls._cursor = None
|
||||
cls._tracer = cls.tracer_provider.get_tracer(__name__)
|
||||
PyMySQLInstrumentor().instrument()
|
||||
cls._connection = pymy.connect(
|
||||
user=MYSQL_USER,
|
||||
password=MYSQL_PASSWORD,
|
||||
host=MYSQL_HOST,
|
||||
port=MYSQL_PORT,
|
||||
database=MYSQL_DB_NAME,
|
||||
)
|
||||
cls._cursor = cls._connection.cursor()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
if cls._connection:
|
||||
cls._connection.close()
|
||||
PyMySQLInstrumentor().uninstrument()
|
||||
|
||||
def validate_spans(self):
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 2)
|
||||
for span in spans:
|
||||
if span.name == "rootSpan":
|
||||
root_span = span
|
||||
else:
|
||||
db_span = span
|
||||
self.assertIsInstance(span.start_time, int)
|
||||
self.assertIsInstance(span.end_time, int)
|
||||
self.assertIsNotNone(root_span)
|
||||
self.assertIsNotNone(db_span)
|
||||
self.assertEqual(root_span.name, "rootSpan")
|
||||
self.assertEqual(db_span.name, "mysql.opentelemetry-tests")
|
||||
self.assertIsNotNone(db_span.parent)
|
||||
self.assertIs(db_span.parent, root_span.get_context())
|
||||
self.assertIs(db_span.kind, trace_api.SpanKind.CLIENT)
|
||||
self.assertEqual(db_span.attributes["db.instance"], MYSQL_DB_NAME)
|
||||
self.assertEqual(db_span.attributes["net.peer.name"], MYSQL_HOST)
|
||||
self.assertEqual(db_span.attributes["net.peer.port"], MYSQL_PORT)
|
||||
|
||||
def test_execute(self):
|
||||
"""Should create a child span for execute
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"):
|
||||
self._cursor.execute("CREATE TABLE IF NOT EXISTS test (id INT)")
|
||||
self.validate_spans()
|
||||
|
||||
def test_executemany(self):
|
||||
"""Should create a child span for executemany
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"):
|
||||
data = (("1",), ("2",), ("3",))
|
||||
stmt = "INSERT INTO test (id) VALUES (%s)"
|
||||
self._cursor.executemany(stmt, data)
|
||||
self.validate_spans()
|
||||
|
||||
def test_callproc(self):
|
||||
"""Should create a child span for callproc
|
||||
"""
|
||||
with self._tracer.start_as_current_span("rootSpan"), self.assertRaises(
|
||||
Exception
|
||||
):
|
||||
self._cursor.callproc("test", ())
|
||||
self.validate_spans()
|
@ -0,0 +1,120 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import redis
|
||||
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.instrumentation.redis import RedisInstrumentor
|
||||
from opentelemetry.test.test_base import TestBase
|
||||
|
||||
|
||||
class TestRedisInstrument(TestBase):
|
||||
|
||||
test_service = "redis"
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.redis_client = redis.Redis(port=6379)
|
||||
self.redis_client.flushall()
|
||||
RedisInstrumentor().instrument(tracer_provider=self.tracer_provider)
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
RedisInstrumentor().uninstrument()
|
||||
|
||||
def _check_span(self, span):
|
||||
self.assertEqual(span.attributes["service"], self.test_service)
|
||||
self.assertEqual(span.name, "redis.command")
|
||||
self.assertIs(
|
||||
span.status.canonical_code, trace.status.StatusCanonicalCode.OK
|
||||
)
|
||||
self.assertEqual(span.attributes.get("db.instance"), 0)
|
||||
self.assertEqual(
|
||||
span.attributes.get("db.url"), "redis://localhost:6379"
|
||||
)
|
||||
|
||||
def test_long_command(self):
|
||||
self.redis_client.mget(*range(1000))
|
||||
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
span = spans[0]
|
||||
self._check_span(span)
|
||||
self.assertTrue(
|
||||
span.attributes.get("db.statement").startswith("MGET 0 1 2 3")
|
||||
)
|
||||
self.assertTrue(span.attributes.get("db.statement").endswith("..."))
|
||||
|
||||
def test_basics(self):
|
||||
self.assertIsNone(self.redis_client.get("cheese"))
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
span = spans[0]
|
||||
self._check_span(span)
|
||||
self.assertEqual(span.attributes.get("db.statement"), "GET cheese")
|
||||
self.assertEqual(span.attributes.get("redis.args_length"), 2)
|
||||
|
||||
def test_pipeline_traced(self):
|
||||
with self.redis_client.pipeline(transaction=False) as pipeline:
|
||||
pipeline.set("blah", 32)
|
||||
pipeline.rpush("foo", "éé")
|
||||
pipeline.hgetall("xxx")
|
||||
pipeline.execute()
|
||||
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
span = spans[0]
|
||||
self._check_span(span)
|
||||
self.assertEqual(
|
||||
span.attributes.get("db.statement"),
|
||||
"SET blah 32\nRPUSH foo éé\nHGETALL xxx",
|
||||
)
|
||||
self.assertEqual(span.attributes.get("redis.pipeline_length"), 3)
|
||||
|
||||
def test_pipeline_immediate(self):
|
||||
with self.redis_client.pipeline() as pipeline:
|
||||
pipeline.set("a", 1)
|
||||
pipeline.immediate_execute_command("SET", "b", 2)
|
||||
pipeline.execute()
|
||||
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
# expecting two separate spans here, rather than a
|
||||
# single span for the whole pipeline
|
||||
self.assertEqual(len(spans), 2)
|
||||
span = spans[0]
|
||||
self._check_span(span)
|
||||
self.assertEqual(span.attributes.get("db.statement"), "SET b 2")
|
||||
|
||||
def test_parent(self):
|
||||
"""Ensure OpenTelemetry works with redis."""
|
||||
ot_tracer = trace.get_tracer("redis_svc")
|
||||
|
||||
with ot_tracer.start_as_current_span("redis_get"):
|
||||
self.assertIsNone(self.redis_client.get("cheese"))
|
||||
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 2)
|
||||
child_span, parent_span = spans[0], spans[1]
|
||||
|
||||
# confirm the parenting
|
||||
self.assertIsNone(parent_span.parent)
|
||||
self.assertIs(child_span.parent, parent_span.get_context())
|
||||
|
||||
self.assertEqual(parent_span.name, "redis_get")
|
||||
self.assertEqual(parent_span.instrumentation_info.name, "redis_svc")
|
||||
|
||||
self.assertEqual(
|
||||
child_span.attributes.get("service"), self.test_service
|
||||
)
|
||||
self.assertEqual(child_span.name, "redis.command")
|
@ -0,0 +1,13 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
@ -0,0 +1,184 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import contextlib
|
||||
|
||||
from sqlalchemy import Column, Integer, String, create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
||||
from opentelemetry.instrumentation.sqlalchemy.engine import _DB, _ROWS, _STMT
|
||||
from opentelemetry.test.test_base import TestBase
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
def _create_engine(engine_args):
|
||||
# create a SQLAlchemy engine
|
||||
config = dict(engine_args)
|
||||
url = config.pop("url")
|
||||
return create_engine(url, **config)
|
||||
|
||||
|
||||
class Player(Base):
|
||||
"""Player entity used to test SQLAlchemy ORM"""
|
||||
|
||||
__tablename__ = "players"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(20))
|
||||
|
||||
|
||||
class SQLAlchemyTestMixin(TestBase):
|
||||
__test__ = False
|
||||
|
||||
"""SQLAlchemy test mixin that includes a complete set of tests
|
||||
that must be executed for different engine. When a new test (or
|
||||
a regression test) should be added to SQLAlchemy test suite, a new
|
||||
entry must be appended here so that it will be executed for all
|
||||
available and supported engines. If the test is specific to only
|
||||
one engine, that test must be added to the specific `TestCase`
|
||||
implementation.
|
||||
|
||||
To support a new engine, create a new `TestCase` that inherits from
|
||||
`SQLAlchemyTestMixin` and `TestCase`. Then you must define the following
|
||||
static class variables:
|
||||
* VENDOR: the database vendor name
|
||||
* SQL_DB: the `db.type` tag that we expect (it's the name of the database available in the `.env` file)
|
||||
* SERVICE: the service that we expect by default
|
||||
* ENGINE_ARGS: all arguments required to create the engine
|
||||
|
||||
To check specific tags in each test, you must implement the
|
||||
`check_meta(self, span)` method.
|
||||
"""
|
||||
|
||||
VENDOR = None
|
||||
SQL_DB = None
|
||||
SERVICE = None
|
||||
ENGINE_ARGS = None
|
||||
|
||||
@contextlib.contextmanager
|
||||
def connection(self):
|
||||
# context manager that provides a connection
|
||||
# to the underlying database
|
||||
try:
|
||||
conn = self.engine.connect()
|
||||
yield conn
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def check_meta(self, span):
|
||||
"""function that can be implemented according to the
|
||||
specific engine implementation
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# create an engine with the given arguments
|
||||
self.engine = _create_engine(self.ENGINE_ARGS)
|
||||
|
||||
# create the database / entities and prepare a session for the test
|
||||
Base.metadata.drop_all(bind=self.engine)
|
||||
Base.metadata.create_all(self.engine, checkfirst=False)
|
||||
self.session = sessionmaker(bind=self.engine)()
|
||||
# trace the engine
|
||||
SQLAlchemyInstrumentor().instrument(
|
||||
engine=self.engine, tracer_provider=self.tracer_provider
|
||||
)
|
||||
self.memory_exporter.clear()
|
||||
|
||||
def tearDown(self):
|
||||
# pylint: disable=invalid-name
|
||||
# clear the database and dispose the engine
|
||||
self.session.close()
|
||||
Base.metadata.drop_all(bind=self.engine)
|
||||
self.engine.dispose()
|
||||
SQLAlchemyInstrumentor().uninstrument()
|
||||
super().tearDown()
|
||||
|
||||
def _check_span(self, span):
|
||||
self.assertEqual(span.name, "{}.query".format(self.VENDOR))
|
||||
self.assertEqual(span.attributes.get("service"), self.SERVICE)
|
||||
self.assertEqual(span.attributes.get(_DB), self.SQL_DB)
|
||||
self.assertIs(
|
||||
span.status.canonical_code, trace.status.StatusCanonicalCode.OK
|
||||
)
|
||||
self.assertGreater((span.end_time - span.start_time), 0)
|
||||
|
||||
def test_orm_insert(self):
|
||||
# ensures that the ORM session is traced
|
||||
wayne = Player(id=1, name="wayne")
|
||||
self.session.add(wayne)
|
||||
self.session.commit()
|
||||
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
span = spans[0]
|
||||
self._check_span(span)
|
||||
self.assertIn("INSERT INTO players", span.attributes.get(_STMT))
|
||||
self.assertEqual(span.attributes.get(_ROWS), 1)
|
||||
self.check_meta(span)
|
||||
|
||||
def test_session_query(self):
|
||||
# ensures that the Session queries are traced
|
||||
out = list(self.session.query(Player).filter_by(name="wayne"))
|
||||
self.assertEqual(len(out), 0)
|
||||
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
span = spans[0]
|
||||
self._check_span(span)
|
||||
self.assertIn(
|
||||
"SELECT players.id AS players_id, players.name AS players_name \nFROM players \nWHERE players.name",
|
||||
span.attributes.get(_STMT),
|
||||
)
|
||||
self.check_meta(span)
|
||||
|
||||
def test_engine_connect_execute(self):
|
||||
# ensures that engine.connect() is properly traced
|
||||
with self.connection() as conn:
|
||||
rows = conn.execute("SELECT * FROM players").fetchall()
|
||||
self.assertEqual(len(rows), 0)
|
||||
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
span = spans[0]
|
||||
self._check_span(span)
|
||||
self.assertEqual(span.attributes.get(_STMT), "SELECT * FROM players")
|
||||
self.check_meta(span)
|
||||
|
||||
def test_parent(self):
|
||||
"""Ensure that sqlalchemy works with opentelemetry."""
|
||||
tracer = self.tracer_provider.get_tracer("sqlalch_svc")
|
||||
|
||||
with tracer.start_as_current_span("sqlalch_op"):
|
||||
with self.connection() as conn:
|
||||
rows = conn.execute("SELECT * FROM players").fetchall()
|
||||
self.assertEqual(len(rows), 0)
|
||||
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 2)
|
||||
child_span, parent_span = spans
|
||||
|
||||
# confirm the parenting
|
||||
self.assertIsNone(parent_span.parent)
|
||||
self.assertIs(child_span.parent, parent_span.get_context())
|
||||
|
||||
self.assertEqual(parent_span.name, "sqlalch_op")
|
||||
self.assertEqual(parent_span.instrumentation_info.name, "sqlalch_svc")
|
||||
|
||||
self.assertEqual(child_span.name, "{}.query".format(self.VENDOR))
|
||||
self.assertEqual(child_span.attributes.get("service"), self.SERVICE)
|
@ -0,0 +1,72 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
||||
from opentelemetry.test.test_base import TestBase
|
||||
|
||||
POSTGRES_CONFIG = {
|
||||
"host": "127.0.0.1",
|
||||
"port": int(os.getenv("TEST_POSTGRES_PORT", "5432")),
|
||||
"user": os.getenv("TEST_POSTGRES_USER", "testuser"),
|
||||
"password": os.getenv("TEST_POSTGRES_PASSWORD", "testpassword"),
|
||||
"dbname": os.getenv("TEST_POSTGRES_DB", "opentelemetry-tests"),
|
||||
}
|
||||
|
||||
|
||||
class SQLAlchemyInstrumentTestCase(TestBase):
|
||||
"""TestCase that checks if the engine is properly traced
|
||||
when the `instrument()` method is used.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
# create a traced engine with the given arguments
|
||||
SQLAlchemyInstrumentor().instrument()
|
||||
dsn = (
|
||||
"postgresql://%(user)s:%(password)s@%(host)s:%(port)s/%(dbname)s"
|
||||
% POSTGRES_CONFIG
|
||||
)
|
||||
self.engine = sqlalchemy.create_engine(dsn)
|
||||
|
||||
# prepare a connection
|
||||
self.conn = self.engine.connect()
|
||||
super().setUp()
|
||||
|
||||
def tearDown(self):
|
||||
# clear the database and dispose the engine
|
||||
self.conn.close()
|
||||
self.engine.dispose()
|
||||
SQLAlchemyInstrumentor().uninstrument()
|
||||
|
||||
def test_engine_traced(self):
|
||||
# ensures that the engine is traced
|
||||
rows = self.conn.execute("SELECT 1").fetchall()
|
||||
self.assertEqual(len(rows), 1)
|
||||
|
||||
traces = self.memory_exporter.get_finished_spans()
|
||||
# trace composition
|
||||
self.assertEqual(len(traces), 1)
|
||||
span = traces[0]
|
||||
# check subset of span fields
|
||||
self.assertEqual(span.name, "postgres.query")
|
||||
self.assertEqual(span.attributes.get("service"), "postgres")
|
||||
self.assertIs(
|
||||
span.status.canonical_code, trace.status.StatusCanonicalCode.OK
|
||||
)
|
||||
self.assertGreater((span.end_time - span.start_time), 0)
|
@ -0,0 +1,83 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.exc import ProgrammingError
|
||||
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.instrumentation.sqlalchemy.engine import (
|
||||
_DB,
|
||||
_HOST,
|
||||
_PORT,
|
||||
_ROWS,
|
||||
_STMT,
|
||||
)
|
||||
|
||||
from .mixins import SQLAlchemyTestMixin
|
||||
|
||||
MYSQL_CONFIG = {
|
||||
"host": "127.0.0.1",
|
||||
"port": int(os.getenv("TEST_MYSQL_PORT", "3306")),
|
||||
"user": os.getenv("TEST_MYSQL_USER", "testuser"),
|
||||
"password": os.getenv("TEST_MYSQL_PASSWORD", "testpassword"),
|
||||
"database": os.getenv("TEST_MYSQL_DATABASE", "opentelemetry-tests"),
|
||||
}
|
||||
|
||||
|
||||
class MysqlConnectorTestCase(SQLAlchemyTestMixin):
|
||||
"""TestCase for mysql-connector engine"""
|
||||
|
||||
__test__ = True
|
||||
|
||||
VENDOR = "mysql"
|
||||
SQL_DB = "opentelemetry-tests"
|
||||
SERVICE = "mysql"
|
||||
ENGINE_ARGS = {
|
||||
"url": "mysql+mysqlconnector://%(user)s:%(password)s@%(host)s:%(port)s/%(database)s"
|
||||
% MYSQL_CONFIG
|
||||
}
|
||||
|
||||
def check_meta(self, span):
|
||||
# check database connection tags
|
||||
self.assertEqual(span.attributes.get(_HOST), MYSQL_CONFIG["host"])
|
||||
self.assertEqual(span.attributes.get(_PORT), MYSQL_CONFIG["port"])
|
||||
|
||||
def test_engine_execute_errors(self):
|
||||
# ensures that SQL errors are reported
|
||||
with pytest.raises(ProgrammingError):
|
||||
with self.connection() as conn:
|
||||
conn.execute("SELECT * FROM a_wrong_table").fetchall()
|
||||
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
span = spans[0]
|
||||
# span fields
|
||||
self.assertEqual(span.name, "{}.query".format(self.VENDOR))
|
||||
self.assertEqual(span.attributes.get("service"), self.SERVICE)
|
||||
self.assertEqual(
|
||||
span.attributes.get(_STMT), "SELECT * FROM a_wrong_table"
|
||||
)
|
||||
self.assertEqual(span.attributes.get(_DB), self.SQL_DB)
|
||||
self.assertIsNone(span.attributes.get(_ROWS))
|
||||
self.check_meta(span)
|
||||
self.assertTrue(span.end_time - span.start_time > 0)
|
||||
# check the error
|
||||
self.assertIs(
|
||||
span.status.canonical_code,
|
||||
trace.status.StatusCanonicalCode.UNKNOWN,
|
||||
)
|
||||
self.assertIn("a_wrong_table", span.status.description)
|
@ -0,0 +1,98 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import psycopg2
|
||||
import pytest
|
||||
from sqlalchemy.exc import ProgrammingError
|
||||
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.instrumentation.sqlalchemy.engine import (
|
||||
_DB,
|
||||
_HOST,
|
||||
_PORT,
|
||||
_ROWS,
|
||||
_STMT,
|
||||
)
|
||||
|
||||
from .mixins import SQLAlchemyTestMixin
|
||||
|
||||
POSTGRES_CONFIG = {
|
||||
"host": "127.0.0.1",
|
||||
"port": int(os.getenv("TEST_POSTGRES_PORT", "5432")),
|
||||
"user": os.getenv("TEST_POSTGRES_USER", "testuser"),
|
||||
"password": os.getenv("TEST_POSTGRES_PASSWORD", "testpassword"),
|
||||
"dbname": os.getenv("TEST_POSTGRES_DB", "opentelemetry-tests"),
|
||||
}
|
||||
|
||||
|
||||
class PostgresTestCase(SQLAlchemyTestMixin):
|
||||
"""TestCase for Postgres Engine"""
|
||||
|
||||
__test__ = True
|
||||
|
||||
VENDOR = "postgres"
|
||||
SQL_DB = "opentelemetry-tests"
|
||||
SERVICE = "postgres"
|
||||
ENGINE_ARGS = {
|
||||
"url": "postgresql://%(user)s:%(password)s@%(host)s:%(port)s/%(dbname)s"
|
||||
% POSTGRES_CONFIG
|
||||
}
|
||||
|
||||
def check_meta(self, span):
|
||||
# check database connection tags
|
||||
self.assertEqual(span.attributes.get(_HOST), POSTGRES_CONFIG["host"])
|
||||
self.assertEqual(span.attributes.get(_PORT), POSTGRES_CONFIG["port"])
|
||||
|
||||
def test_engine_execute_errors(self):
|
||||
# ensures that SQL errors are reported
|
||||
with pytest.raises(ProgrammingError):
|
||||
with self.connection() as conn:
|
||||
conn.execute("SELECT * FROM a_wrong_table").fetchall()
|
||||
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
span = spans[0]
|
||||
# span fields
|
||||
self.assertEqual(span.name, "{}.query".format(self.VENDOR))
|
||||
self.assertEqual(span.attributes.get("service"), self.SERVICE)
|
||||
self.assertEqual(
|
||||
span.attributes.get(_STMT), "SELECT * FROM a_wrong_table"
|
||||
)
|
||||
self.assertEqual(span.attributes.get(_DB), self.SQL_DB)
|
||||
self.assertIsNone(span.attributes.get(_ROWS))
|
||||
self.check_meta(span)
|
||||
self.assertTrue(span.end_time - span.start_time > 0)
|
||||
# check the error
|
||||
self.assertIs(
|
||||
span.status.canonical_code,
|
||||
trace.status.StatusCanonicalCode.UNKNOWN,
|
||||
)
|
||||
self.assertIn("a_wrong_table", span.status.description)
|
||||
|
||||
|
||||
class PostgresCreatorTestCase(PostgresTestCase):
|
||||
"""TestCase for Postgres Engine that includes the same tests set
|
||||
of `PostgresTestCase`, but it uses a specific `creator` function.
|
||||
"""
|
||||
|
||||
VENDOR = "postgres"
|
||||
SQL_DB = "opentelemetry-tests"
|
||||
SERVICE = "postgres"
|
||||
ENGINE_ARGS = {
|
||||
"url": "postgresql://",
|
||||
"creator": lambda: psycopg2.connect(**POSTGRES_CONFIG),
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.exc import OperationalError
|
||||
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.instrumentation.sqlalchemy.engine import _DB, _ROWS, _STMT
|
||||
|
||||
from .mixins import SQLAlchemyTestMixin
|
||||
|
||||
|
||||
class SQLiteTestCase(SQLAlchemyTestMixin):
|
||||
"""TestCase for the SQLite engine"""
|
||||
|
||||
__test__ = True
|
||||
|
||||
VENDOR = "sqlite"
|
||||
SQL_DB = ":memory:"
|
||||
SERVICE = "sqlite"
|
||||
ENGINE_ARGS = {"url": "sqlite:///:memory:"}
|
||||
|
||||
def test_engine_execute_errors(self):
|
||||
# ensures that SQL errors are reported
|
||||
with pytest.raises(OperationalError):
|
||||
with self.connection() as conn:
|
||||
conn.execute("SELECT * FROM a_wrong_table").fetchall()
|
||||
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
span = spans[0]
|
||||
# span fields
|
||||
self.assertEqual(span.name, "{}.query".format(self.VENDOR))
|
||||
self.assertEqual(span.attributes.get("service"), self.SERVICE)
|
||||
self.assertEqual(
|
||||
span.attributes.get(_STMT), "SELECT * FROM a_wrong_table"
|
||||
)
|
||||
self.assertEqual(span.attributes.get(_DB), self.SQL_DB)
|
||||
self.assertIsNone(span.attributes.get(_ROWS))
|
||||
self.assertTrue((span.end_time - span.start_time) > 0)
|
||||
# check the error
|
||||
self.assertIs(
|
||||
span.status.canonical_code,
|
||||
trace.status.StatusCanonicalCode.UNKNOWN,
|
||||
)
|
||||
self.assertEqual(
|
||||
span.status.description, "no such table: a_wrong_table"
|
||||
)
|
Reference in New Issue
Block a user