__init__.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. # Copyright The OpenTelemetry Authors
  2. # SPDX-License-Identifier: Apache-2.0
  3. from __future__ import annotations
  4. import logging
  5. import os
  6. from contextvars import Token
  7. from uuid import uuid4
  8. # pylint: disable=wrong-import-position
  9. from opentelemetry.context.context import Context, _RuntimeContext # noqa
  10. from opentelemetry.context.contextvars_context import ContextVarsRuntimeContext
  11. from opentelemetry.environment_variables import OTEL_PYTHON_CONTEXT
  12. logger = logging.getLogger(__name__)
  13. def _load_runtime_context() -> _RuntimeContext:
  14. """Initialize the RuntimeContext
  15. Returns:
  16. An instance of RuntimeContext.
  17. """
  18. configured_context = os.environ.get(OTEL_PYTHON_CONTEXT)
  19. if not configured_context:
  20. return ContextVarsRuntimeContext()
  21. # pylint: disable=import-outside-toplevel,no-name-in-module
  22. from opentelemetry.util._importlib_metadata import ( # noqa: PLC0415
  23. entry_points,
  24. )
  25. try:
  26. return next(
  27. iter(
  28. entry_points(
  29. group="opentelemetry_context", name=configured_context
  30. )
  31. )
  32. ).load()()
  33. except Exception: # pylint: disable=broad-exception-caught
  34. logger.exception(
  35. "Failed to load context: %s, falling back to contextvars_context",
  36. configured_context,
  37. )
  38. return ContextVarsRuntimeContext()
  39. _RUNTIME_CONTEXT = _load_runtime_context()
  40. def create_key(keyname: str) -> str:
  41. """To allow cross-cutting concern to control access to their local state,
  42. the RuntimeContext API provides a function which takes a keyname as input,
  43. and returns a unique key.
  44. Args:
  45. keyname: The key name is for debugging purposes and is not required to be unique.
  46. Returns:
  47. A unique string representing the newly created key.
  48. """
  49. return keyname + "-" + str(uuid4())
  50. def get_value(key: str, context: Context | None = None) -> object:
  51. """To access the local state of a concern, the RuntimeContext API
  52. provides a function which takes a context and a key as input,
  53. and returns a value.
  54. Args:
  55. key: The key of the value to retrieve.
  56. context: The context from which to retrieve the value, if None, the current context is used.
  57. Returns:
  58. The value associated with the key.
  59. """
  60. return context.get(key) if context is not None else get_current().get(key)
  61. def set_value(
  62. key: str, value: object, context: Context | None = None
  63. ) -> Context:
  64. """To record the local state of a cross-cutting concern, the
  65. RuntimeContext API provides a function which takes a context, a
  66. key, and a value as input, and returns an updated context
  67. which contains the new value.
  68. Args:
  69. key: The key of the entry to set.
  70. value: The value of the entry to set.
  71. context: The context to copy, if None, the current context is used.
  72. Returns:
  73. A new `Context` containing the value set.
  74. """
  75. if context is None:
  76. context = get_current()
  77. new_values = context.copy()
  78. new_values[key] = value
  79. return Context(new_values)
  80. def get_current() -> Context:
  81. """To access the context associated with program execution,
  82. the Context API provides a function which takes no arguments
  83. and returns a Context.
  84. Returns:
  85. The current `Context` object.
  86. """
  87. return _RUNTIME_CONTEXT.get_current()
  88. def attach(context: Context) -> Token[Context]:
  89. """Associates a Context with the caller's current execution unit. Returns
  90. a token that can be used to restore the previous Context.
  91. Args:
  92. context: The Context to set as current.
  93. Returns:
  94. A token that can be used with `detach` to reset the context.
  95. """
  96. return _RUNTIME_CONTEXT.attach(context)
  97. def detach(token: Token[Context]) -> None:
  98. """Resets the Context associated with the caller's current execution unit
  99. to the value it had before attaching a specified Context.
  100. Args:
  101. token: The Token that was returned by a previous call to attach a Context.
  102. """
  103. try:
  104. _RUNTIME_CONTEXT.detach(token)
  105. except Exception: # pylint: disable=broad-exception-caught
  106. logger.exception("Failed to detach context")
  107. # FIXME This is a temporary location for the suppress instrumentation key.
  108. # Once the decision around how to suppress instrumentation is made in the
  109. # spec, this key should be moved accordingly.
  110. _ON_EMIT_RECURSION_COUNT_KEY = create_key("on_emit_recursion_count")
  111. _SUPPRESS_INSTRUMENTATION_KEY = create_key("suppress_instrumentation")
  112. _SUPPRESS_HTTP_INSTRUMENTATION_KEY = create_key(
  113. "suppress_http_instrumentation"
  114. )
  115. __all__ = [
  116. "Context",
  117. "attach",
  118. "create_key",
  119. "detach",
  120. "get_current",
  121. "get_value",
  122. "set_value",
  123. ]