Skip to content
This repository has been archived by the owner on Oct 8, 2024. It is now read-only.

solution and test for tracer multi-import issue #161

Merged
merged 5 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions lib/charms/tempo_k8s/v1/charm_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,6 @@ def _remove_stale_otel_sdk_packages():

_remove_stale_otel_sdk_packages()


import functools
import inspect
import logging
Expand Down Expand Up @@ -271,7 +270,7 @@ def _remove_stale_otel_sdk_packages():
# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version

LIBPATCH = 14
LIBPATCH = 15

PYDEPS = ["opentelemetry-exporter-otlp-proto-http==1.21.0"]

Expand All @@ -281,7 +280,6 @@ def _remove_stale_otel_sdk_packages():
# set this to 0 if you are debugging/developing this library source
dev_logger.setLevel(logging.CRITICAL)


_CharmType = Type[CharmBase] # the type CharmBase and any subclass thereof
_C = TypeVar("_C", bound=_CharmType)
_T = TypeVar("_T", bound=type)
Expand Down Expand Up @@ -333,9 +331,17 @@ def _get_tracer() -> Optional[Tracer]:
try:
return tracer.get()
except LookupError:
# fallback: this course-corrects for a user error where charm_tracing symbols are imported
# from different paths (typically charms.tempo_k8s... and lib.charms.tempo_k8s...)
try:
ctx: Context = copy_context()
if context_tracer := _get_tracer_from_context(ctx):
logger.warning(
"Tracer not found in `tracer` context var. "
"Verify that you're importing all `charm_tracing` symbols from the same module path."
"For more info: https://python-notes.curiousefficiency.org/en/latest/python"
"_concepts/import_traps.html#the-double-import-trap"
)
PietroPasotti marked this conversation as resolved.
Show resolved Hide resolved
return context_tracer.get()
else:
return None
Expand Down
67 changes: 67 additions & 0 deletions tests/scenario/test_a_charm_tracer_multi_import_warning.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# WARNING keep this test module on top of the scenario tests, else the
# imports from previous tests will pollute the sys.modules and cause this test to fail.
PietroPasotti marked this conversation as resolved.
Show resolved Hide resolved
# I know this is horrible but yea, couldn't find a better way to fix the issue. Tried:
# - delete from sys.modules all modules containing 'charms.tempo_k8s'
# - delete from sys.modules all modules containing 'otlp_http'


from unittest.mock import patch

# this test file is intentionally quite broken, don't modify the imports
# import autoinstrument and get_current_span from charms.[...]
PietroPasotti marked this conversation as resolved.
Show resolved Hide resolved
from charms.tempo_k8s.v1.charm_tracing import _autoinstrument as autoinstrument
from ops import CharmBase
from scenario import Context, State

# import trace from lib.charms.[...]
from lib.charms.tempo_k8s.v1.charm_tracing import trace


@trace
def _my_fn(foo):
return foo + 1


class MyCharmSimpleEvent(CharmBase):
META = {"name": "margherita"}

def __init__(self, fw):
super().__init__(fw)
fw.observe(self.on.start, self._on_start)

def _on_start(self, _):
_my_fn(2)

@property
def tempo(self):
return "foo.bar:80"


autoinstrument(MyCharmSimpleEvent, "tempo")


def test_charm_tracer_multi_import_warning(caplog, monkeypatch):
"""Check that we warn the user in case the test is importing tracing symbols from multiple paths.

See https://python-notes.curiousefficiency.org/en/latest/python_concepts/import_traps.html#the-double-import-trap
for a great explanation of what's going on.
"""
import opentelemetry

with patch(
"opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter.export"
) as f:
f.return_value = opentelemetry.sdk.trace.export.SpanExportResult.SUCCESS
ctx = Context(MyCharmSimpleEvent, meta=MyCharmSimpleEvent.META)
with caplog.at_level("WARNING"):
ctx.run("start", State())

spans = f.call_args_list[0].args[0]
assert [span.name for span in spans] == [
# we're only able to capture the _my_fn span because of the fallback behaviour
"function call: _my_fn",
"method call: MyCharmSimpleEvent._on_start",
"event: start",
"margherita/0: start event",
]
assert "Tracer not found in `tracer` context var." in caplog.records[0].message
4 changes: 1 addition & 3 deletions tests/scenario/test_charm_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import pytest
import scenario
from charms.tempo_k8s.v1.charm_tracing import CHARM_TRACING_ENABLED
from charms.tempo_k8s.v1.charm_tracing import CHARM_TRACING_ENABLED, get_current_span, trace
from charms.tempo_k8s.v1.charm_tracing import _autoinstrument as autoinstrument
from charms.tempo_k8s.v2.tracing import (
ProtocolType,
Expand All @@ -19,8 +19,6 @@
from scenario import Context, State
from scenario.runtime import UncaughtCharmError

from lib.charms.tempo_k8s.v1.charm_tracing import get_current_span, trace

os.environ[CHARM_TRACING_ENABLED] = "1"

logger = logging.getLogger(__name__)
Expand Down
Loading