lmnr 0.4.53.dev0__py3-none-any.whl → 0.7.26__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lmnr/__init__.py +32 -11
- lmnr/cli/__init__.py +270 -0
- lmnr/cli/datasets.py +371 -0
- lmnr/cli/evals.py +111 -0
- lmnr/cli/rules.py +42 -0
- lmnr/opentelemetry_lib/__init__.py +70 -0
- lmnr/opentelemetry_lib/decorators/__init__.py +337 -0
- lmnr/opentelemetry_lib/litellm/__init__.py +685 -0
- lmnr/opentelemetry_lib/litellm/utils.py +100 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/__init__.py +849 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/config.py +13 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_emitter.py +211 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_models.py +41 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/span_utils.py +401 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/streaming.py +425 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/utils.py +332 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/version.py +1 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/claude_agent/__init__.py +451 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/claude_agent/proxy.py +144 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_agent/__init__.py +100 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/__init__.py +476 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/utils.py +12 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py +599 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/config.py +9 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/schema_utils.py +26 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py +330 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/__init__.py +488 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/config.py +8 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_emitter.py +143 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_models.py +41 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/span_utils.py +229 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/utils.py +92 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/version.py +1 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/kernel/__init__.py +381 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/kernel/utils.py +36 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py +121 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/utils.py +60 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/__init__.py +61 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/__init__.py +472 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/chat_wrappers.py +1185 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/completion_wrappers.py +305 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/config.py +16 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +312 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_emitter.py +100 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_models.py +41 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py +68 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/utils.py +197 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v0/__init__.py +176 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/__init__.py +368 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +325 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py +135 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/responses_wrappers.py +786 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/version.py +1 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openhands_ai/__init__.py +388 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/opentelemetry/__init__.py +69 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/skyvern/__init__.py +191 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/threading/__init__.py +197 -0
- lmnr/opentelemetry_lib/tracing/__init__.py +263 -0
- lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +516 -0
- lmnr/{openllmetry_sdk → opentelemetry_lib}/tracing/attributes.py +21 -8
- lmnr/opentelemetry_lib/tracing/context.py +200 -0
- lmnr/opentelemetry_lib/tracing/exporter.py +153 -0
- lmnr/opentelemetry_lib/tracing/instruments.py +140 -0
- lmnr/opentelemetry_lib/tracing/processor.py +193 -0
- lmnr/opentelemetry_lib/tracing/span.py +398 -0
- lmnr/opentelemetry_lib/tracing/tracer.py +57 -0
- lmnr/opentelemetry_lib/tracing/utils.py +62 -0
- lmnr/opentelemetry_lib/utils/package_check.py +18 -0
- lmnr/opentelemetry_lib/utils/wrappers.py +11 -0
- lmnr/sdk/browser/__init__.py +0 -0
- lmnr/sdk/browser/background_send_events.py +158 -0
- lmnr/sdk/browser/browser_use_cdp_otel.py +100 -0
- lmnr/sdk/browser/browser_use_otel.py +142 -0
- lmnr/sdk/browser/bubus_otel.py +71 -0
- lmnr/sdk/browser/cdp_utils.py +518 -0
- lmnr/sdk/browser/inject_script.js +514 -0
- lmnr/sdk/browser/patchright_otel.py +151 -0
- lmnr/sdk/browser/playwright_otel.py +322 -0
- lmnr/sdk/browser/pw_utils.py +363 -0
- lmnr/sdk/browser/recorder/record.umd.min.cjs +84 -0
- lmnr/sdk/browser/utils.py +70 -0
- lmnr/sdk/client/asynchronous/async_client.py +180 -0
- lmnr/sdk/client/asynchronous/resources/__init__.py +6 -0
- lmnr/sdk/client/asynchronous/resources/base.py +32 -0
- lmnr/sdk/client/asynchronous/resources/browser_events.py +41 -0
- lmnr/sdk/client/asynchronous/resources/datasets.py +131 -0
- lmnr/sdk/client/asynchronous/resources/evals.py +266 -0
- lmnr/sdk/client/asynchronous/resources/evaluators.py +85 -0
- lmnr/sdk/client/asynchronous/resources/tags.py +83 -0
- lmnr/sdk/client/synchronous/resources/__init__.py +6 -0
- lmnr/sdk/client/synchronous/resources/base.py +32 -0
- lmnr/sdk/client/synchronous/resources/browser_events.py +40 -0
- lmnr/sdk/client/synchronous/resources/datasets.py +131 -0
- lmnr/sdk/client/synchronous/resources/evals.py +263 -0
- lmnr/sdk/client/synchronous/resources/evaluators.py +85 -0
- lmnr/sdk/client/synchronous/resources/tags.py +83 -0
- lmnr/sdk/client/synchronous/sync_client.py +191 -0
- lmnr/sdk/datasets/__init__.py +94 -0
- lmnr/sdk/datasets/file_utils.py +91 -0
- lmnr/sdk/decorators.py +163 -26
- lmnr/sdk/eval_control.py +3 -2
- lmnr/sdk/evaluations.py +403 -191
- lmnr/sdk/laminar.py +1080 -549
- lmnr/sdk/log.py +7 -2
- lmnr/sdk/types.py +246 -134
- lmnr/sdk/utils.py +151 -7
- lmnr/version.py +46 -0
- {lmnr-0.4.53.dev0.dist-info → lmnr-0.7.26.dist-info}/METADATA +152 -106
- lmnr-0.7.26.dist-info/RECORD +116 -0
- lmnr-0.7.26.dist-info/WHEEL +4 -0
- lmnr-0.7.26.dist-info/entry_points.txt +3 -0
- lmnr/cli.py +0 -101
- lmnr/openllmetry_sdk/.python-version +0 -1
- lmnr/openllmetry_sdk/__init__.py +0 -72
- lmnr/openllmetry_sdk/config/__init__.py +0 -9
- lmnr/openllmetry_sdk/decorators/base.py +0 -185
- lmnr/openllmetry_sdk/instruments.py +0 -38
- lmnr/openllmetry_sdk/tracing/__init__.py +0 -1
- lmnr/openllmetry_sdk/tracing/content_allow_list.py +0 -24
- lmnr/openllmetry_sdk/tracing/context_manager.py +0 -13
- lmnr/openllmetry_sdk/tracing/tracing.py +0 -884
- lmnr/openllmetry_sdk/utils/in_memory_span_exporter.py +0 -61
- lmnr/openllmetry_sdk/utils/package_check.py +0 -7
- lmnr/openllmetry_sdk/version.py +0 -1
- lmnr/sdk/datasets.py +0 -55
- lmnr-0.4.53.dev0.dist-info/LICENSE +0 -75
- lmnr-0.4.53.dev0.dist-info/RECORD +0 -33
- lmnr-0.4.53.dev0.dist-info/WHEEL +0 -4
- lmnr-0.4.53.dev0.dist-info/entry_points.txt +0 -3
- /lmnr/{openllmetry_sdk → opentelemetry_lib}/.flake8 +0 -0
- /lmnr/{openllmetry_sdk → opentelemetry_lib}/utils/__init__.py +0 -0
- /lmnr/{openllmetry_sdk → opentelemetry_lib}/utils/json_encoder.py +0 -0
- /lmnr/{openllmetry_sdk/decorators/__init__.py → py.typed} +0 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Copyright The OpenTelemetry Authors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""
|
|
15
|
+
Instrument threading to propagate OpenTelemetry context.
|
|
16
|
+
|
|
17
|
+
Copied from opentelemetry-instrumentation-threading at commit:
|
|
18
|
+
ad2fe813abb2ab0b6e25bedeebef5041ca3189f7
|
|
19
|
+
https://github.com/open-telemetry/opentelemetry-python-contrib/blob/ad2fe813abb2ab0b6e25bedeebef5041ca3189f7/instrumentation/opentelemetry-instrumentation-threading/src/opentelemetry/instrumentation/threading/__init__.py
|
|
20
|
+
|
|
21
|
+
Modified to use the Laminar isolated context.
|
|
22
|
+
|
|
23
|
+
Usage
|
|
24
|
+
-----
|
|
25
|
+
|
|
26
|
+
.. code-block:: python
|
|
27
|
+
|
|
28
|
+
from opentelemetry.instrumentation.threading import ThreadingInstrumentor
|
|
29
|
+
|
|
30
|
+
ThreadingInstrumentor().instrument()
|
|
31
|
+
|
|
32
|
+
This library provides instrumentation for the `threading` module to ensure that
|
|
33
|
+
the OpenTelemetry context is propagated across threads. It is important to note
|
|
34
|
+
that this instrumentation does not produce any telemetry data on its own. It
|
|
35
|
+
merely ensures that the context is correctly propagated when threads are used.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
When instrumented, new threads created using threading.Thread, threading.Timer,
|
|
39
|
+
or within futures.ThreadPoolExecutor will have the current OpenTelemetry
|
|
40
|
+
context attached, and this context will be re-activated in the thread's
|
|
41
|
+
run method or the executor's worker thread."
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
from __future__ import annotations
|
|
45
|
+
|
|
46
|
+
import threading
|
|
47
|
+
from concurrent import futures
|
|
48
|
+
from typing import TYPE_CHECKING, Any, Callable, Collection
|
|
49
|
+
|
|
50
|
+
from wrapt import (
|
|
51
|
+
wrap_function_wrapper, # type: ignore[reportUnknownVariableType]
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
from lmnr.opentelemetry_lib.tracing.context import (
|
|
55
|
+
get_current_context,
|
|
56
|
+
attach_context,
|
|
57
|
+
detach_context,
|
|
58
|
+
)
|
|
59
|
+
from opentelemetry import context
|
|
60
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
61
|
+
from opentelemetry.instrumentation.utils import unwrap
|
|
62
|
+
|
|
63
|
+
_instruments = ()
|
|
64
|
+
|
|
65
|
+
if TYPE_CHECKING:
|
|
66
|
+
from typing import Protocol, TypeVar
|
|
67
|
+
|
|
68
|
+
R = TypeVar("R")
|
|
69
|
+
|
|
70
|
+
class HasOtelContext(Protocol):
|
|
71
|
+
_otel_context: context.Context
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ThreadingInstrumentor(BaseInstrumentor):
|
|
75
|
+
__WRAPPER_START_METHOD = "start"
|
|
76
|
+
__WRAPPER_RUN_METHOD = "run"
|
|
77
|
+
__WRAPPER_SUBMIT_METHOD = "submit"
|
|
78
|
+
|
|
79
|
+
def instrumentation_dependencies(self) -> Collection[str]:
|
|
80
|
+
return _instruments
|
|
81
|
+
|
|
82
|
+
def _instrument(self, **kwargs: Any):
|
|
83
|
+
self._instrument_thread()
|
|
84
|
+
self._instrument_timer()
|
|
85
|
+
self._instrument_thread_pool()
|
|
86
|
+
|
|
87
|
+
def _uninstrument(self, **kwargs: Any):
|
|
88
|
+
self._uninstrument_thread()
|
|
89
|
+
self._uninstrument_timer()
|
|
90
|
+
self._uninstrument_thread_pool()
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def _instrument_thread():
|
|
94
|
+
wrap_function_wrapper(
|
|
95
|
+
threading.Thread,
|
|
96
|
+
ThreadingInstrumentor.__WRAPPER_START_METHOD,
|
|
97
|
+
ThreadingInstrumentor.__wrap_threading_start,
|
|
98
|
+
)
|
|
99
|
+
wrap_function_wrapper(
|
|
100
|
+
threading.Thread,
|
|
101
|
+
ThreadingInstrumentor.__WRAPPER_RUN_METHOD,
|
|
102
|
+
ThreadingInstrumentor.__wrap_threading_run,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def _instrument_timer():
|
|
107
|
+
wrap_function_wrapper(
|
|
108
|
+
threading.Timer,
|
|
109
|
+
ThreadingInstrumentor.__WRAPPER_START_METHOD,
|
|
110
|
+
ThreadingInstrumentor.__wrap_threading_start,
|
|
111
|
+
)
|
|
112
|
+
wrap_function_wrapper(
|
|
113
|
+
threading.Timer,
|
|
114
|
+
ThreadingInstrumentor.__WRAPPER_RUN_METHOD,
|
|
115
|
+
ThreadingInstrumentor.__wrap_threading_run,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def _instrument_thread_pool():
|
|
120
|
+
wrap_function_wrapper(
|
|
121
|
+
futures.ThreadPoolExecutor,
|
|
122
|
+
ThreadingInstrumentor.__WRAPPER_SUBMIT_METHOD,
|
|
123
|
+
ThreadingInstrumentor.__wrap_thread_pool_submit,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def _uninstrument_thread():
|
|
128
|
+
unwrap(threading.Thread, ThreadingInstrumentor.__WRAPPER_START_METHOD)
|
|
129
|
+
unwrap(threading.Thread, ThreadingInstrumentor.__WRAPPER_RUN_METHOD)
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def _uninstrument_timer():
|
|
133
|
+
unwrap(threading.Timer, ThreadingInstrumentor.__WRAPPER_START_METHOD)
|
|
134
|
+
unwrap(threading.Timer, ThreadingInstrumentor.__WRAPPER_RUN_METHOD)
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def _uninstrument_thread_pool():
|
|
138
|
+
unwrap(
|
|
139
|
+
futures.ThreadPoolExecutor,
|
|
140
|
+
ThreadingInstrumentor.__WRAPPER_SUBMIT_METHOD,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def __wrap_threading_start(
|
|
145
|
+
call_wrapped: Callable[[], None],
|
|
146
|
+
instance: HasOtelContext,
|
|
147
|
+
args: tuple[()],
|
|
148
|
+
kwargs: dict[str, Any],
|
|
149
|
+
) -> None:
|
|
150
|
+
instance._lmnr_otel_context = get_current_context()
|
|
151
|
+
return call_wrapped(*args, **kwargs)
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def __wrap_threading_run(
|
|
155
|
+
call_wrapped: Callable[..., R],
|
|
156
|
+
instance: HasOtelContext,
|
|
157
|
+
args: tuple[Any, ...],
|
|
158
|
+
kwargs: dict[str, Any],
|
|
159
|
+
) -> R:
|
|
160
|
+
token = None
|
|
161
|
+
try:
|
|
162
|
+
# Genearally, this must be set in __wrap_threading_start, but it is
|
|
163
|
+
# possible to Thread().run() without Thread().start(), so in that case,
|
|
164
|
+
# we need to capture the context here.
|
|
165
|
+
# We still want to capture the context in __wrap_threading_start,
|
|
166
|
+
# in order to stay close to the original implementation.
|
|
167
|
+
if not hasattr(instance, "_lmnr_otel_context"):
|
|
168
|
+
instance._lmnr_otel_context = get_current_context()
|
|
169
|
+
token = attach_context(instance._lmnr_otel_context)
|
|
170
|
+
return call_wrapped(*args, **kwargs)
|
|
171
|
+
finally:
|
|
172
|
+
if token is not None:
|
|
173
|
+
detach_context(token)
|
|
174
|
+
|
|
175
|
+
@staticmethod
|
|
176
|
+
def __wrap_thread_pool_submit(
|
|
177
|
+
call_wrapped: Callable[..., R],
|
|
178
|
+
instance: futures.ThreadPoolExecutor,
|
|
179
|
+
args: tuple[Callable[..., Any], ...],
|
|
180
|
+
kwargs: dict[str, Any],
|
|
181
|
+
) -> R:
|
|
182
|
+
# obtain the original function and wrapped kwargs
|
|
183
|
+
original_func = args[0]
|
|
184
|
+
otel_context = get_current_context()
|
|
185
|
+
|
|
186
|
+
def wrapped_func(*func_args: Any, **func_kwargs: Any) -> R:
|
|
187
|
+
token = None
|
|
188
|
+
try:
|
|
189
|
+
token = attach_context(otel_context)
|
|
190
|
+
return original_func(*func_args, **func_kwargs)
|
|
191
|
+
finally:
|
|
192
|
+
if token is not None:
|
|
193
|
+
detach_context(token)
|
|
194
|
+
|
|
195
|
+
# replace the original function with the wrapped function
|
|
196
|
+
new_args: tuple[Callable[..., Any], ...] = (wrapped_func,) + args[1:]
|
|
197
|
+
return call_wrapped(*new_args, **kwargs)
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import logging
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
from lmnr.opentelemetry_lib.tracing.processor import LaminarSpanProcessor
|
|
6
|
+
from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
|
|
7
|
+
from lmnr.sdk.types import SessionRecordingOptions
|
|
8
|
+
from lmnr.sdk.log import VerboseColorfulFormatter
|
|
9
|
+
from lmnr.opentelemetry_lib.tracing.instruments import (
|
|
10
|
+
Instruments,
|
|
11
|
+
init_instrumentations,
|
|
12
|
+
)
|
|
13
|
+
from lmnr.opentelemetry_lib.tracing.context import (
|
|
14
|
+
attach_context,
|
|
15
|
+
clear_context,
|
|
16
|
+
pop_span_context as ctx_pop_span_context,
|
|
17
|
+
get_current_context,
|
|
18
|
+
get_token_stack,
|
|
19
|
+
push_span_context as ctx_push_span_context,
|
|
20
|
+
set_token_stack,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from opentelemetry import trace
|
|
24
|
+
from opentelemetry.context import Context
|
|
25
|
+
|
|
26
|
+
# instead of importing from opentelemetry.instrumentation.threading,
|
|
27
|
+
# we import from our modified copy to use Laminar's isolated context.
|
|
28
|
+
from ..opentelemetry.instrumentation.threading import ThreadingInstrumentor
|
|
29
|
+
from opentelemetry.sdk.resources import Resource
|
|
30
|
+
from opentelemetry.sdk.trace import TracerProvider, SpanProcessor
|
|
31
|
+
from opentelemetry.sdk.trace.export import SpanExporter
|
|
32
|
+
|
|
33
|
+
TRACER_NAME = "lmnr.tracer"
|
|
34
|
+
|
|
35
|
+
MAX_EVENTS_OR_ATTRIBUTES_PER_SPAN = 5000
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TracerWrapper(object):
|
|
39
|
+
resource_attributes: dict = {}
|
|
40
|
+
enable_content_tracing: bool = True
|
|
41
|
+
session_recording_options: SessionRecordingOptions = {}
|
|
42
|
+
_lock = threading.Lock()
|
|
43
|
+
_tracer_provider: TracerProvider | None = None
|
|
44
|
+
_logger: logging.Logger
|
|
45
|
+
_async_client: AsyncLaminarClient
|
|
46
|
+
_resource: Resource
|
|
47
|
+
_span_processor: SpanProcessor
|
|
48
|
+
_original_thread_init = None
|
|
49
|
+
|
|
50
|
+
def __new__(
|
|
51
|
+
cls,
|
|
52
|
+
disable_batch=False,
|
|
53
|
+
exporter: SpanExporter | None = None,
|
|
54
|
+
instruments: set[Instruments] | None = None,
|
|
55
|
+
block_instruments: set[Instruments] | None = None,
|
|
56
|
+
base_url: str | None = None,
|
|
57
|
+
port: int = 8443,
|
|
58
|
+
http_port: int = 443,
|
|
59
|
+
project_api_key: str | None = None,
|
|
60
|
+
max_export_batch_size: int | None = None,
|
|
61
|
+
force_http: bool = False,
|
|
62
|
+
timeout_seconds: int = 30,
|
|
63
|
+
set_global_tracer_provider: bool = True,
|
|
64
|
+
otel_logger_level: int = logging.ERROR,
|
|
65
|
+
session_recording_options: SessionRecordingOptions | None = None,
|
|
66
|
+
) -> "TracerWrapper":
|
|
67
|
+
# Silence some opentelemetry warnings
|
|
68
|
+
logging.getLogger("opentelemetry.trace").setLevel(otel_logger_level)
|
|
69
|
+
|
|
70
|
+
base_http_url = f"{base_url}:{http_port}" if base_url else None
|
|
71
|
+
with cls._lock:
|
|
72
|
+
if not hasattr(cls, "instance"):
|
|
73
|
+
cls._initialize_logger(cls)
|
|
74
|
+
obj = super(TracerWrapper, cls).__new__(cls)
|
|
75
|
+
|
|
76
|
+
# Store session recording options
|
|
77
|
+
cls.session_recording_options = session_recording_options or {}
|
|
78
|
+
|
|
79
|
+
if project_api_key:
|
|
80
|
+
obj._async_client = AsyncLaminarClient(
|
|
81
|
+
base_url=base_http_url or "https://api.lmnr.ai",
|
|
82
|
+
project_api_key=project_api_key,
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
obj._async_client = None
|
|
86
|
+
|
|
87
|
+
obj._resource = Resource(attributes=TracerWrapper.resource_attributes)
|
|
88
|
+
|
|
89
|
+
obj._span_processor = LaminarSpanProcessor(
|
|
90
|
+
base_url=base_url,
|
|
91
|
+
api_key=project_api_key,
|
|
92
|
+
port=http_port if force_http else port,
|
|
93
|
+
exporter=exporter,
|
|
94
|
+
max_export_batch_size=max_export_batch_size,
|
|
95
|
+
timeout_seconds=timeout_seconds,
|
|
96
|
+
force_http=force_http,
|
|
97
|
+
disable_batch=disable_batch,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
lmnr_provider = TracerProvider(resource=obj._resource)
|
|
101
|
+
global_provider = trace.get_tracer_provider()
|
|
102
|
+
if set_global_tracer_provider and isinstance(
|
|
103
|
+
global_provider, trace.ProxyTracerProvider
|
|
104
|
+
):
|
|
105
|
+
trace.set_tracer_provider(lmnr_provider)
|
|
106
|
+
|
|
107
|
+
obj._tracer_provider = lmnr_provider
|
|
108
|
+
|
|
109
|
+
obj._tracer_provider.add_span_processor(obj._span_processor)
|
|
110
|
+
|
|
111
|
+
# Setup threading context inheritance
|
|
112
|
+
obj._setup_threading_inheritance()
|
|
113
|
+
|
|
114
|
+
# This is not a real instrumentation and does not generate telemetry
|
|
115
|
+
# data, but it is required to ensure that OpenTelemetry context
|
|
116
|
+
# propagation is enabled.
|
|
117
|
+
# See the README at:
|
|
118
|
+
# https://pypi.org/project/opentelemetry-instrumentation-threading/
|
|
119
|
+
ThreadingInstrumentor().instrument()
|
|
120
|
+
|
|
121
|
+
init_instrumentations(
|
|
122
|
+
tracer_provider=obj._tracer_provider,
|
|
123
|
+
instruments=instruments,
|
|
124
|
+
block_instruments=block_instruments,
|
|
125
|
+
async_client=obj._async_client,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
cls.instance = obj
|
|
129
|
+
|
|
130
|
+
# Force flushes for debug environments (e.g. local development)
|
|
131
|
+
atexit.register(obj.exit_handler)
|
|
132
|
+
|
|
133
|
+
return cls.instance
|
|
134
|
+
|
|
135
|
+
def _setup_threading_inheritance(self):
|
|
136
|
+
"""Setup threading inheritance for isolated context."""
|
|
137
|
+
if TracerWrapper._original_thread_init is None:
|
|
138
|
+
# Monkey patch Thread.__init__ to capture context inheritance
|
|
139
|
+
TracerWrapper._original_thread_init = threading.Thread.__init__
|
|
140
|
+
|
|
141
|
+
def patched_thread_init(thread_self, *args, **kwargs):
|
|
142
|
+
# Capture current isolated context and token stack for inheritance
|
|
143
|
+
current_context = get_current_context()
|
|
144
|
+
current_token_stack = get_token_stack().copy()
|
|
145
|
+
|
|
146
|
+
# Get the original target function
|
|
147
|
+
original_target = kwargs.get("target")
|
|
148
|
+
if not original_target and args:
|
|
149
|
+
original_target = args[0]
|
|
150
|
+
|
|
151
|
+
# Only inherit if we have a target function
|
|
152
|
+
if original_target:
|
|
153
|
+
# Create a wrapper function that sets up context
|
|
154
|
+
def thread_wrapper(*target_args, **target_kwargs):
|
|
155
|
+
# Set inherited context and token stack in the new thread
|
|
156
|
+
attach_context(current_context)
|
|
157
|
+
set_token_stack(current_token_stack)
|
|
158
|
+
# Run original target
|
|
159
|
+
return original_target(*target_args, **target_kwargs)
|
|
160
|
+
|
|
161
|
+
# Replace the target with our wrapper
|
|
162
|
+
if "target" in kwargs:
|
|
163
|
+
kwargs["target"] = thread_wrapper
|
|
164
|
+
elif args:
|
|
165
|
+
args = (thread_wrapper,) + args[1:]
|
|
166
|
+
|
|
167
|
+
# Call original init
|
|
168
|
+
TracerWrapper._original_thread_init(thread_self, *args, **kwargs)
|
|
169
|
+
|
|
170
|
+
threading.Thread.__init__ = patched_thread_init
|
|
171
|
+
|
|
172
|
+
def exit_handler(self):
|
|
173
|
+
if isinstance(self._span_processor, LaminarSpanProcessor):
|
|
174
|
+
self._span_processor.clear()
|
|
175
|
+
self.flush()
|
|
176
|
+
|
|
177
|
+
def _initialize_logger(self):
|
|
178
|
+
self._logger = logging.getLogger(__name__)
|
|
179
|
+
console_log_handler = logging.StreamHandler()
|
|
180
|
+
console_log_handler.setFormatter(VerboseColorfulFormatter())
|
|
181
|
+
self._logger.addHandler(console_log_handler)
|
|
182
|
+
|
|
183
|
+
def get_isolated_context(self) -> Context:
|
|
184
|
+
"""Get the current isolated context."""
|
|
185
|
+
return get_current_context()
|
|
186
|
+
|
|
187
|
+
def push_span_context(self, span: trace.Span) -> Context:
|
|
188
|
+
"""Push a new context with the given span onto the stack."""
|
|
189
|
+
current_ctx = get_current_context()
|
|
190
|
+
new_context = trace.set_span_in_context(span, current_ctx)
|
|
191
|
+
ctx_push_span_context(new_context)
|
|
192
|
+
return new_context
|
|
193
|
+
|
|
194
|
+
def pop_span_context(self) -> None:
|
|
195
|
+
"""Pop the current span context from the stack."""
|
|
196
|
+
ctx_pop_span_context()
|
|
197
|
+
|
|
198
|
+
@staticmethod
|
|
199
|
+
def set_static_params(
|
|
200
|
+
resource_attributes: dict,
|
|
201
|
+
enable_content_tracing: bool,
|
|
202
|
+
) -> None:
|
|
203
|
+
TracerWrapper.resource_attributes = resource_attributes
|
|
204
|
+
TracerWrapper.enable_content_tracing = enable_content_tracing
|
|
205
|
+
|
|
206
|
+
@classmethod
|
|
207
|
+
def verify_initialized(cls) -> bool:
|
|
208
|
+
# This is not using lock, but it is fine to return False from here
|
|
209
|
+
# even if initialization is going on.
|
|
210
|
+
|
|
211
|
+
# If we try to acquire the lock here, it may deadlock if an automatic
|
|
212
|
+
# instrumentation is importing a file that (at the top level) has a
|
|
213
|
+
# function annotated with Laminar's `observe` decorator.
|
|
214
|
+
# The decorator is evaluated at import time, inside `init_instrumentations`,
|
|
215
|
+
# which is called by the `TracerWrapper` constructor while holding the lock.
|
|
216
|
+
# Without the lock here, we will simply return False, which will cause
|
|
217
|
+
# the decorator to return the original function. This is fine, at runtime,
|
|
218
|
+
# the next import statement will re-evaluate the decorator, and Laminar will
|
|
219
|
+
# have been initialized by that time.
|
|
220
|
+
return hasattr(cls, "instance") and hasattr(cls.instance, "_span_processor")
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def clear(cls):
|
|
224
|
+
if not cls.verify_initialized():
|
|
225
|
+
return
|
|
226
|
+
# Any state cleanup. Now used in between tests
|
|
227
|
+
if isinstance(cls.instance._span_processor, LaminarSpanProcessor):
|
|
228
|
+
cls.instance._span_processor.clear()
|
|
229
|
+
# Clear the isolated context state for clean test state
|
|
230
|
+
clear_context()
|
|
231
|
+
|
|
232
|
+
def shutdown(self):
|
|
233
|
+
if self._tracer_provider is None:
|
|
234
|
+
return
|
|
235
|
+
self._tracer_provider.shutdown()
|
|
236
|
+
|
|
237
|
+
def flush(self):
|
|
238
|
+
if not hasattr(self, "_span_processor"):
|
|
239
|
+
self._logger.warning("TracerWrapper not fully initialized, cannot flush")
|
|
240
|
+
return False
|
|
241
|
+
return self._span_processor.force_flush()
|
|
242
|
+
|
|
243
|
+
def force_reinit_processor(self):
|
|
244
|
+
if isinstance(self._span_processor, LaminarSpanProcessor):
|
|
245
|
+
self._span_processor.force_flush()
|
|
246
|
+
self._span_processor.force_reinit()
|
|
247
|
+
# Clear the isolated context to prevent subsequent invocations
|
|
248
|
+
# (e.g., in Lambda) from continuing traces from previous invocations
|
|
249
|
+
clear_context()
|
|
250
|
+
else:
|
|
251
|
+
self._logger.warning(
|
|
252
|
+
"Not using LaminarSpanProcessor, cannot force reinit processor"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
@classmethod
|
|
256
|
+
def get_session_recording_options(cls) -> SessionRecordingOptions:
|
|
257
|
+
"""Get the session recording options set during initialization."""
|
|
258
|
+
return cls.session_recording_options
|
|
259
|
+
|
|
260
|
+
def get_tracer(self) -> trace.Tracer:
|
|
261
|
+
if self._tracer_provider is None:
|
|
262
|
+
return trace.get_tracer_provider().get_tracer(TRACER_NAME)
|
|
263
|
+
return self._tracer_provider.get_tracer(TRACER_NAME)
|