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
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(iter(entry_points(group="opentelemetry_context", name=configured_context))).load()()
36 except Exception: # pylint: disable=broad-exception-caught
37 logger.exception(
38 "Failed to load context: %s, falling back to contextvars_context",
39 configured_context,
40 )
41 return ContextVarsRuntimeContext()
42
43
44_RUNTIME_CONTEXT = _load_runtime_context()
45
46
47def create_key(keyname: str) -> str:
48 """To allow cross-cutting concern to control access to their local state,
49 the RuntimeContext API provides a function which takes a keyname as input,
50 and returns a unique key.
51 Args:
52 keyname: The key name is for debugging purposes and is not required to be unique.
53 Returns:
54 A unique string representing the newly created key.
55 """
56 return keyname + "-" + str(uuid4())
57
58
59def get_value(key: str, context: Context | None = None) -> object:
60 """To access the local state of a concern, the RuntimeContext API
61 provides a function which takes a context and a key as input,
62 and returns a value.
63
64 Args:
65 key: The key of the value to retrieve.
66 context: The context from which to retrieve the value, if None, the current context is used.
67
68 Returns:
69 The value associated with the key.
70 """
71 return context.get(key) if context is not None else get_current().get(key)
72
73
74def set_value(key: str, value: object, context: Context | None = None) -> Context:
75 """To record the local state of a cross-cutting concern, the
76 RuntimeContext API provides a function which takes a context, a
77 key, and a value as input, and returns an updated context
78 which contains the new value.
79
80 Args:
81 key: The key of the entry to set.
82 value: The value of the entry to set.
83 context: The context to copy, if None, the current context is used.
84
85 Returns:
86 A new `Context` containing the value set.
87 """
88 if context is None:
89 context = get_current()
90 new_values = context.copy()
91 new_values[key] = value
92 return Context(new_values)
93
94
95def get_current() -> Context:
96 """To access the context associated with program execution,
97 the Context API provides a function which takes no arguments
98 and returns a Context.
99
100 Returns:
101 The current `Context` object.
102 """
103 return _RUNTIME_CONTEXT.get_current()
104
105
106def attach(context: Context) -> Token[Context]:
107 """Associates a Context with the caller's current execution unit. Returns
108 a token that can be used to restore the previous Context.
109
110 Args:
111 context: The Context to set as current.
112
113 Returns:
114 A token that can be used with `detach` to reset the context.
115 """
116 return _RUNTIME_CONTEXT.attach(context)
117
118
119def detach(token: Token[Context]) -> None:
120 """Resets the Context associated with the caller's current execution unit
121 to the value it had before attaching a specified Context.
122
123 Args:
124 token: The Token that was returned by a previous call to attach a Context.
125 """
126 try:
127 _RUNTIME_CONTEXT.detach(token)
128 except Exception: # pylint: disable=broad-exception-caught
129 logger.exception("Failed to detach context")
130
131
132# FIXME This is a temporary location for the suppress instrumentation key.
133# Once the decision around how to suppress instrumentation is made in the
134# spec, this key should be moved accordingly.
135_ON_EMIT_RECURSION_COUNT_KEY = create_key("on_emit_recursion_count")
136_SUPPRESS_INSTRUMENTATION_KEY = create_key("suppress_instrumentation")
137_SUPPRESS_HTTP_INSTRUMENTATION_KEY = create_key("suppress_http_instrumentation")
138
139__all__ = [
140 "Context",
141 "attach",
142 "create_key",
143 "detach",
144 "get_current",
145 "get_value",
146 "set_value",
147]