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