Skip to content

Instantly share code, notes, and snippets.

@xrmx
Last active May 8, 2024 08:57
Show Gist options
  • Save xrmx/fc81e710e7a860ca229839b10e780dfd to your computer and use it in GitHub Desktop.
Save xrmx/fc81e710e7a860ca229839b10e780dfd to your computer and use it in GitHub Desktop.
opentelementry api context change hook
diff --git a/opentelemetry-api/src/opentelemetry/context/__init__.py b/opentelemetry-api/src/opentelemetry/context/__init__.py
index 0a2785ab..7243eb59 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 importlib import import_module
from os import environ
from uuid import uuid4
@@ -25,6 +26,38 @@ from opentelemetry.util._importlib_metadata import entry_points
logger = logging.getLogger(__name__)
+def import_string(dotted_path: string) -> object:
+ try:
+ module_path, class_name = dotted_path.rsplit(".", 1)
+ except ValueError:
+ msg = f'"{dotted_path}" doesn\'t look like a module path'
+ raise ImportError(msg)
+
+ module = import_module(module_path)
+
+ try:
+ return getattr(module, class_name)
+ except AttributeError:
+ msg = f'Module "{module_path}" does not define a "{class_name}" attribute/class'
+ raise ImportError(msg)
+
+class DefaultHookHandler:
+ def send(self, context):
+ pass
+
+def _load_context_change_hook() -> None:
+ # FIXME: should I just use an entry point instead?
+ context_change_hook = environ.get(OTEL_PYTHON_CONTEXT_CHANGE_HOOK)
+ if context_change_hook:
+ try:
+ return import_string(context_change_hooks)()
+ except ImportError:
+ logger.exception(
+ "Failed to load context change hook",
+ )
+ return DefaultHookHandler()
+
+
def _load_runtime_context() -> _RuntimeContext:
"""Initialize the RuntimeContext
@@ -67,6 +100,7 @@ def _load_runtime_context() -> _RuntimeContext:
_RUNTIME_CONTEXT = _load_runtime_context()
+_CONTEXT_CHANGE_HOOK = _load_context_change_hook()
def create_key(keyname: str) -> str:
@@ -140,7 +174,9 @@ def attach(context: Context) -> object:
Returns:
A token that can be used with `detach` to reset the context.
"""
- return _RUNTIME_CONTEXT.attach(context)
+ token = _RUNTIME_CONTEXT.attach(context)
+ _CONTEXT_CHANGE_HOOK.send(context)
+ return token
def detach(token: object) -> None:
@@ -154,6 +190,8 @@ def detach(token: object) -> None:
_RUNTIME_CONTEXT.detach(token)
except Exception: # pylint: disable=broad-except
logger.exception("Failed to detach context")
+ else:
+ _CONTEXT_CHANGE_HOOK.send(get_current())
# FIXME This is a temporary location for the suppress instrumentation key.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment