lmnr 0.5.3__py3-none-any.whl → 0.6.1__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 +6 -1
- lmnr/opentelemetry_lib/__init__.py +23 -36
- lmnr/opentelemetry_lib/decorators/__init__.py +219 -0
- lmnr/opentelemetry_lib/tracing/__init__.py +158 -1
- lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +398 -0
- lmnr/opentelemetry_lib/tracing/attributes.py +14 -7
- lmnr/opentelemetry_lib/tracing/context_properties.py +53 -0
- lmnr/opentelemetry_lib/tracing/exporter.py +60 -0
- lmnr/opentelemetry_lib/tracing/instruments.py +121 -0
- lmnr/opentelemetry_lib/tracing/processor.py +96 -0
- lmnr/opentelemetry_lib/tracing/{context_manager.py → tracer.py} +6 -1
- lmnr/opentelemetry_lib/utils/package_check.py +3 -1
- lmnr/sdk/browser/browser_use_otel.py +1 -1
- lmnr/sdk/browser/playwright_otel.py +22 -7
- lmnr/sdk/browser/pw_utils.py +25 -9
- lmnr/sdk/client/asynchronous/resources/agent.py +3 -1
- lmnr/sdk/client/synchronous/resources/agent.py +3 -1
- lmnr/sdk/decorators.py +4 -2
- lmnr/sdk/evaluations.py +3 -3
- lmnr/sdk/laminar.py +28 -31
- lmnr/sdk/utils.py +2 -3
- lmnr/version.py +1 -1
- {lmnr-0.5.3.dist-info → lmnr-0.6.1.dist-info}/METADATA +65 -62
- {lmnr-0.5.3.dist-info → lmnr-0.6.1.dist-info}/RECORD +27 -28
- lmnr/opentelemetry_lib/config/__init__.py +0 -12
- lmnr/opentelemetry_lib/decorators/base.py +0 -210
- lmnr/opentelemetry_lib/instruments.py +0 -42
- lmnr/opentelemetry_lib/tracing/content_allow_list.py +0 -24
- lmnr/opentelemetry_lib/tracing/tracing.py +0 -1016
- lmnr/opentelemetry_lib/utils/in_memory_span_exporter.py +0 -61
- {lmnr-0.5.3.dist-info → lmnr-0.6.1.dist-info}/LICENSE +0 -0
- {lmnr-0.5.3.dist-info → lmnr-0.6.1.dist-info}/WHEEL +0 -0
- {lmnr-0.5.3.dist-info → lmnr-0.6.1.dist-info}/entry_points.txt +0 -0
lmnr/__init__.py
CHANGED
@@ -13,8 +13,10 @@ from .sdk.types import (
|
|
13
13
|
)
|
14
14
|
from .sdk.decorators import observe
|
15
15
|
from .sdk.types import LaminarSpanContext
|
16
|
-
from .opentelemetry_lib import Instruments
|
17
16
|
from .opentelemetry_lib.tracing.attributes import Attributes
|
17
|
+
from .opentelemetry_lib.tracing.instruments import Instruments
|
18
|
+
from .opentelemetry_lib.tracing.processor import LaminarSpanProcessor
|
19
|
+
from .opentelemetry_lib.tracing.tracer import get_laminar_tracer_provider, get_tracer
|
18
20
|
from opentelemetry.trace import use_span
|
19
21
|
|
20
22
|
__all__ = [
|
@@ -29,9 +31,12 @@ __all__ = [
|
|
29
31
|
"LaminarClient",
|
30
32
|
"LaminarDataset",
|
31
33
|
"LaminarSpanContext",
|
34
|
+
"LaminarSpanProcessor",
|
32
35
|
"RunAgentResponseChunk",
|
33
36
|
"StepChunkContent",
|
34
37
|
"TracingLevel",
|
38
|
+
"get_laminar_tracer_provider",
|
39
|
+
"get_tracer",
|
35
40
|
"evaluate",
|
36
41
|
"observe",
|
37
42
|
"use_span",
|
@@ -1,19 +1,14 @@
|
|
1
|
+
import logging
|
1
2
|
import sys
|
2
3
|
|
3
4
|
from typing import Optional, Set
|
4
|
-
from opentelemetry.sdk.trace import SpanProcessor
|
5
5
|
from opentelemetry.sdk.trace.export import SpanExporter
|
6
6
|
from opentelemetry.sdk.resources import SERVICE_NAME
|
7
|
-
from opentelemetry.propagators.textmap import TextMapPropagator
|
8
|
-
from opentelemetry.util.re import parse_env_headers
|
9
7
|
|
10
|
-
from lmnr.opentelemetry_lib.instruments import Instruments
|
11
|
-
from lmnr.opentelemetry_lib.
|
12
|
-
|
13
|
-
|
14
|
-
)
|
15
|
-
from lmnr.opentelemetry_lib.tracing.tracing import TracerWrapper
|
16
|
-
from typing import Dict
|
8
|
+
from lmnr.opentelemetry_lib.tracing.instruments import Instruments
|
9
|
+
from lmnr.opentelemetry_lib.tracing import TracerWrapper
|
10
|
+
|
11
|
+
MAX_MANUAL_SPAN_PAYLOAD_SIZE = 1024 * 1024 # 1MB
|
17
12
|
|
18
13
|
|
19
14
|
class TracerManager:
|
@@ -22,48 +17,40 @@ class TracerManager:
|
|
22
17
|
@staticmethod
|
23
18
|
def init(
|
24
19
|
app_name: Optional[str] = sys.argv[0],
|
25
|
-
api_endpoint: str = "https://api.lmnr.ai",
|
26
|
-
api_key: Optional[str] = None,
|
27
|
-
headers: Dict[str, str] = {},
|
28
20
|
disable_batch=False,
|
29
21
|
exporter: Optional[SpanExporter] = None,
|
30
|
-
processor: Optional[SpanProcessor] = None,
|
31
|
-
propagator: Optional[TextMapPropagator] = None,
|
32
|
-
should_enrich_metrics: bool = False,
|
33
22
|
resource_attributes: dict = {},
|
34
23
|
instruments: Optional[Set[Instruments]] = None,
|
35
|
-
|
24
|
+
block_instruments: Optional[Set[Instruments]] = None,
|
25
|
+
base_url: str = "https://api.lmnr.ai",
|
26
|
+
port: int = 8443,
|
27
|
+
http_port: int = 443,
|
36
28
|
project_api_key: Optional[str] = None,
|
37
29
|
max_export_batch_size: Optional[int] = None,
|
30
|
+
force_http: bool = False,
|
31
|
+
timeout_seconds: int = 30,
|
32
|
+
set_global_tracer_provider: bool = True,
|
33
|
+
otel_logger_level: int = logging.ERROR,
|
38
34
|
) -> None:
|
39
|
-
|
40
|
-
return
|
41
|
-
|
42
|
-
enable_content_tracing = is_content_tracing_enabled()
|
43
|
-
|
44
|
-
if isinstance(headers, str):
|
45
|
-
headers = parse_env_headers(headers)
|
46
|
-
|
47
|
-
if api_key and not exporter and not processor and not headers:
|
48
|
-
headers = {
|
49
|
-
"Authorization": f"Bearer {api_key}",
|
50
|
-
}
|
35
|
+
enable_content_tracing = True
|
51
36
|
|
52
37
|
# Tracer init
|
53
38
|
resource_attributes.update({SERVICE_NAME: app_name})
|
54
|
-
TracerWrapper.set_static_params(
|
55
|
-
resource_attributes, enable_content_tracing, api_endpoint, headers
|
56
|
-
)
|
39
|
+
TracerWrapper.set_static_params(resource_attributes, enable_content_tracing)
|
57
40
|
TracerManager.__tracer_wrapper = TracerWrapper(
|
58
41
|
disable_batch=disable_batch,
|
59
|
-
processor=processor,
|
60
|
-
propagator=propagator,
|
61
42
|
exporter=exporter,
|
62
|
-
should_enrich_metrics=should_enrich_metrics,
|
63
43
|
instruments=instruments,
|
64
|
-
|
44
|
+
block_instruments=block_instruments,
|
45
|
+
base_url=base_url,
|
46
|
+
port=port,
|
47
|
+
http_port=http_port,
|
65
48
|
project_api_key=project_api_key,
|
66
49
|
max_export_batch_size=max_export_batch_size,
|
50
|
+
force_http=force_http,
|
51
|
+
timeout_seconds=timeout_seconds,
|
52
|
+
set_global_tracer_provider=set_global_tracer_provider,
|
53
|
+
otel_logger_level=otel_logger_level,
|
67
54
|
)
|
68
55
|
|
69
56
|
@staticmethod
|
@@ -0,0 +1,219 @@
|
|
1
|
+
from functools import wraps
|
2
|
+
import json
|
3
|
+
import logging
|
4
|
+
import pydantic
|
5
|
+
import types
|
6
|
+
from typing import Any, Literal, Optional, Union
|
7
|
+
|
8
|
+
from opentelemetry import trace
|
9
|
+
from opentelemetry import context as context_api
|
10
|
+
from opentelemetry.trace import Span
|
11
|
+
|
12
|
+
from lmnr.sdk.utils import get_input_from_func_args, is_method
|
13
|
+
from lmnr.opentelemetry_lib import MAX_MANUAL_SPAN_PAYLOAD_SIZE
|
14
|
+
from lmnr.opentelemetry_lib.tracing.tracer import get_tracer
|
15
|
+
from lmnr.opentelemetry_lib.tracing.attributes import SPAN_INPUT, SPAN_OUTPUT, SPAN_TYPE
|
16
|
+
from lmnr.opentelemetry_lib.tracing import TracerWrapper
|
17
|
+
from lmnr.opentelemetry_lib.utils.json_encoder import JSONEncoder
|
18
|
+
|
19
|
+
|
20
|
+
class CustomJSONEncoder(JSONEncoder):
|
21
|
+
def default(self, o: Any) -> Any:
|
22
|
+
if isinstance(o, pydantic.BaseModel):
|
23
|
+
return o.model_dump_json()
|
24
|
+
try:
|
25
|
+
return super().default(o)
|
26
|
+
except TypeError:
|
27
|
+
return str(o) # Fallback to string representation for unsupported types
|
28
|
+
|
29
|
+
|
30
|
+
def json_dumps(data: dict) -> str:
|
31
|
+
try:
|
32
|
+
return json.dumps(data, cls=CustomJSONEncoder)
|
33
|
+
except Exception:
|
34
|
+
# Log the exception and return a placeholder if serialization completely fails
|
35
|
+
logging.warning("Failed to serialize data to JSON, type: %s", type(data))
|
36
|
+
return "{}" # Return an empty JSON object as a fallback
|
37
|
+
|
38
|
+
|
39
|
+
def entity_method(
|
40
|
+
name: Optional[str] = None,
|
41
|
+
ignore_input: bool = False,
|
42
|
+
ignore_inputs: Optional[list[str]] = None,
|
43
|
+
ignore_output: bool = False,
|
44
|
+
span_type: Union[Literal["DEFAULT"], Literal["LLM"], Literal["TOOL"]] = "DEFAULT",
|
45
|
+
):
|
46
|
+
def decorate(fn):
|
47
|
+
@wraps(fn)
|
48
|
+
def wrap(*args, **kwargs):
|
49
|
+
if not TracerWrapper.verify_initialized():
|
50
|
+
return fn(*args, **kwargs)
|
51
|
+
|
52
|
+
span_name = name or fn.__name__
|
53
|
+
|
54
|
+
with get_tracer() as tracer:
|
55
|
+
span = tracer.start_span(span_name, attributes={SPAN_TYPE: span_type})
|
56
|
+
|
57
|
+
ctx = trace.set_span_in_context(span, context_api.get_current())
|
58
|
+
ctx_token = context_api.attach(ctx)
|
59
|
+
|
60
|
+
try:
|
61
|
+
if not ignore_input:
|
62
|
+
inp = json_dumps(
|
63
|
+
get_input_from_func_args(
|
64
|
+
fn,
|
65
|
+
is_method=is_method(fn),
|
66
|
+
func_args=args,
|
67
|
+
func_kwargs=kwargs,
|
68
|
+
ignore_inputs=ignore_inputs,
|
69
|
+
)
|
70
|
+
)
|
71
|
+
if len(inp) > MAX_MANUAL_SPAN_PAYLOAD_SIZE:
|
72
|
+
span.set_attribute(
|
73
|
+
SPAN_INPUT, "Laminar: input too large to record"
|
74
|
+
)
|
75
|
+
else:
|
76
|
+
span.set_attribute(SPAN_INPUT, inp)
|
77
|
+
except TypeError:
|
78
|
+
pass
|
79
|
+
|
80
|
+
try:
|
81
|
+
res = fn(*args, **kwargs)
|
82
|
+
except Exception as e:
|
83
|
+
_process_exception(span, e)
|
84
|
+
span.end()
|
85
|
+
raise e
|
86
|
+
|
87
|
+
# span will be ended in the generator
|
88
|
+
if isinstance(res, types.GeneratorType):
|
89
|
+
return _handle_generator(span, ctx_token, res)
|
90
|
+
if isinstance(res, types.AsyncGeneratorType):
|
91
|
+
# async def foo() -> AsyncGenerator[int, None]:
|
92
|
+
# is not considered async in a classical sense in Python,
|
93
|
+
# so we handle this inside the sync wrapper.
|
94
|
+
# In particular, CO_COROUTINE is different from CO_ASYNC_GENERATOR.
|
95
|
+
# Flags are listed from LSB here:
|
96
|
+
# https://docs.python.org/3/library/inspect.html#inspect-module-co-flags
|
97
|
+
# See also: https://groups.google.com/g/python-tulip/c/6rWweGXLutU?pli=1
|
98
|
+
return _ahandle_generator(span, ctx_token, res)
|
99
|
+
|
100
|
+
try:
|
101
|
+
if not ignore_output:
|
102
|
+
output = json_dumps(res)
|
103
|
+
if len(output) > MAX_MANUAL_SPAN_PAYLOAD_SIZE:
|
104
|
+
span.set_attribute(
|
105
|
+
SPAN_OUTPUT, "Laminar: output too large to record"
|
106
|
+
)
|
107
|
+
else:
|
108
|
+
span.set_attribute(SPAN_OUTPUT, output)
|
109
|
+
except TypeError:
|
110
|
+
pass
|
111
|
+
|
112
|
+
span.end()
|
113
|
+
context_api.detach(ctx_token)
|
114
|
+
|
115
|
+
return res
|
116
|
+
|
117
|
+
return wrap
|
118
|
+
|
119
|
+
return decorate
|
120
|
+
|
121
|
+
|
122
|
+
# Async Decorators
|
123
|
+
def aentity_method(
|
124
|
+
name: Optional[str] = None,
|
125
|
+
ignore_input: bool = False,
|
126
|
+
ignore_inputs: Optional[list[str]] = None,
|
127
|
+
ignore_output: bool = False,
|
128
|
+
span_type: Union[Literal["DEFAULT"], Literal["LLM"], Literal["TOOL"]] = "DEFAULT",
|
129
|
+
):
|
130
|
+
def decorate(fn):
|
131
|
+
@wraps(fn)
|
132
|
+
async def wrap(*args, **kwargs):
|
133
|
+
if not TracerWrapper.verify_initialized():
|
134
|
+
return await fn(*args, **kwargs)
|
135
|
+
|
136
|
+
span_name = name or fn.__name__
|
137
|
+
|
138
|
+
with get_tracer() as tracer:
|
139
|
+
span = tracer.start_span(span_name, attributes={SPAN_TYPE: span_type})
|
140
|
+
|
141
|
+
ctx = trace.set_span_in_context(span, context_api.get_current())
|
142
|
+
ctx_token = context_api.attach(ctx)
|
143
|
+
|
144
|
+
try:
|
145
|
+
if not ignore_input:
|
146
|
+
inp = json_dumps(
|
147
|
+
get_input_from_func_args(
|
148
|
+
fn,
|
149
|
+
is_method=is_method(fn),
|
150
|
+
func_args=args,
|
151
|
+
func_kwargs=kwargs,
|
152
|
+
ignore_inputs=ignore_inputs,
|
153
|
+
)
|
154
|
+
)
|
155
|
+
if len(inp) > MAX_MANUAL_SPAN_PAYLOAD_SIZE:
|
156
|
+
span.set_attribute(
|
157
|
+
SPAN_INPUT, "Laminar: input too large to record"
|
158
|
+
)
|
159
|
+
else:
|
160
|
+
span.set_attribute(SPAN_INPUT, inp)
|
161
|
+
except TypeError:
|
162
|
+
pass
|
163
|
+
|
164
|
+
try:
|
165
|
+
res = await fn(*args, **kwargs)
|
166
|
+
except Exception as e:
|
167
|
+
_process_exception(span, e)
|
168
|
+
span.end()
|
169
|
+
raise e
|
170
|
+
|
171
|
+
# span will be ended in the generator
|
172
|
+
if isinstance(res, types.AsyncGeneratorType):
|
173
|
+
# probably unreachable, read the comment in the similar
|
174
|
+
# part of the sync wrapper.
|
175
|
+
return await _ahandle_generator(span, ctx_token, res)
|
176
|
+
|
177
|
+
try:
|
178
|
+
if not ignore_output:
|
179
|
+
output = json_dumps(res)
|
180
|
+
if len(output) > MAX_MANUAL_SPAN_PAYLOAD_SIZE:
|
181
|
+
span.set_attribute(
|
182
|
+
SPAN_OUTPUT, "Laminar: output too large to record"
|
183
|
+
)
|
184
|
+
else:
|
185
|
+
span.set_attribute(SPAN_OUTPUT, output)
|
186
|
+
except TypeError:
|
187
|
+
pass
|
188
|
+
|
189
|
+
span.end()
|
190
|
+
context_api.detach(ctx_token)
|
191
|
+
|
192
|
+
return res
|
193
|
+
|
194
|
+
return wrap
|
195
|
+
|
196
|
+
return decorate
|
197
|
+
|
198
|
+
|
199
|
+
def _handle_generator(span, ctx_token, res):
|
200
|
+
yield from res
|
201
|
+
|
202
|
+
span.end()
|
203
|
+
if ctx_token is not None:
|
204
|
+
context_api.detach(ctx_token)
|
205
|
+
|
206
|
+
|
207
|
+
async def _ahandle_generator(span, ctx_token, res):
|
208
|
+
# async with contextlib.aclosing(res) as closing_gen:
|
209
|
+
async for part in res:
|
210
|
+
yield part
|
211
|
+
|
212
|
+
span.end()
|
213
|
+
if ctx_token is not None:
|
214
|
+
context_api.detach(ctx_token)
|
215
|
+
|
216
|
+
|
217
|
+
def _process_exception(span: Span, e: Exception):
|
218
|
+
# Note that this `escaped` is sent as a StringValue("True"), not a boolean.
|
219
|
+
span.record_exception(e, escaped=True)
|
@@ -1 +1,158 @@
|
|
1
|
-
|
1
|
+
import atexit
|
2
|
+
import logging
|
3
|
+
|
4
|
+
from lmnr.opentelemetry_lib.tracing.processor import LaminarSpanProcessor
|
5
|
+
from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
|
6
|
+
from lmnr.sdk.client.synchronous.sync_client import LaminarClient
|
7
|
+
from lmnr.sdk.log import VerboseColorfulFormatter
|
8
|
+
from lmnr.opentelemetry_lib.tracing.instruments import (
|
9
|
+
Instruments,
|
10
|
+
init_instrumentations,
|
11
|
+
)
|
12
|
+
|
13
|
+
from opentelemetry import trace
|
14
|
+
from opentelemetry.instrumentation.threading import ThreadingInstrumentor
|
15
|
+
from opentelemetry.sdk.resources import Resource
|
16
|
+
from opentelemetry.sdk.trace import TracerProvider, SpanProcessor
|
17
|
+
from opentelemetry.sdk.trace.export import SpanExporter
|
18
|
+
from typing import Optional, Set
|
19
|
+
|
20
|
+
module_logger = logging.getLogger(__name__)
|
21
|
+
console_log_handler = logging.StreamHandler()
|
22
|
+
console_log_handler.setFormatter(VerboseColorfulFormatter())
|
23
|
+
module_logger.addHandler(console_log_handler)
|
24
|
+
|
25
|
+
|
26
|
+
TRACER_NAME = "lmnr.tracer"
|
27
|
+
|
28
|
+
MAX_EVENTS_OR_ATTRIBUTES_PER_SPAN = 5000
|
29
|
+
|
30
|
+
|
31
|
+
class TracerWrapper(object):
|
32
|
+
resource_attributes: dict = {}
|
33
|
+
enable_content_tracing: bool = True
|
34
|
+
__tracer_provider: Optional[TracerProvider] = None
|
35
|
+
__logger: logging.Logger
|
36
|
+
__client: LaminarClient
|
37
|
+
__async_client: AsyncLaminarClient
|
38
|
+
__resource: Resource
|
39
|
+
__span_processor: SpanProcessor
|
40
|
+
|
41
|
+
def __new__(
|
42
|
+
cls,
|
43
|
+
disable_batch=False,
|
44
|
+
exporter: Optional[SpanExporter] = None,
|
45
|
+
instruments: Optional[Set[Instruments]] = None,
|
46
|
+
block_instruments: Optional[Set[Instruments]] = None,
|
47
|
+
base_url: str = "https://api.lmnr.ai",
|
48
|
+
port: int = 8443,
|
49
|
+
http_port: int = 443,
|
50
|
+
project_api_key: Optional[str] = None,
|
51
|
+
max_export_batch_size: Optional[int] = None,
|
52
|
+
force_http: bool = False,
|
53
|
+
timeout_seconds: int = 10,
|
54
|
+
set_global_tracer_provider: bool = True,
|
55
|
+
otel_logger_level: int = logging.ERROR,
|
56
|
+
) -> "TracerWrapper":
|
57
|
+
# Silence some opentelemetry warnings
|
58
|
+
logging.getLogger("opentelemetry.trace").setLevel(otel_logger_level)
|
59
|
+
|
60
|
+
base_http_url = f"{base_url}:{http_port}"
|
61
|
+
cls._initialize_logger(cls)
|
62
|
+
if not hasattr(cls, "instance"):
|
63
|
+
obj = cls.instance = super(TracerWrapper, cls).__new__(cls)
|
64
|
+
|
65
|
+
obj.__client = LaminarClient(
|
66
|
+
base_url=base_http_url,
|
67
|
+
project_api_key=project_api_key,
|
68
|
+
)
|
69
|
+
obj.__async_client = AsyncLaminarClient(
|
70
|
+
base_url=base_http_url,
|
71
|
+
project_api_key=project_api_key,
|
72
|
+
)
|
73
|
+
|
74
|
+
obj.__resource = Resource(attributes=TracerWrapper.resource_attributes)
|
75
|
+
|
76
|
+
obj.__span_processor = LaminarSpanProcessor(
|
77
|
+
base_url=base_url,
|
78
|
+
api_key=project_api_key,
|
79
|
+
port=http_port if force_http else port,
|
80
|
+
exporter=exporter,
|
81
|
+
max_export_batch_size=max_export_batch_size,
|
82
|
+
timeout_seconds=timeout_seconds,
|
83
|
+
force_http=force_http,
|
84
|
+
disable_batch=disable_batch,
|
85
|
+
)
|
86
|
+
|
87
|
+
lmnr_provider = TracerProvider(resource=obj.__resource)
|
88
|
+
global_provider = trace.get_tracer_provider()
|
89
|
+
if set_global_tracer_provider and isinstance(
|
90
|
+
global_provider, trace.ProxyTracerProvider
|
91
|
+
):
|
92
|
+
trace.set_tracer_provider(lmnr_provider)
|
93
|
+
|
94
|
+
obj.__tracer_provider = lmnr_provider
|
95
|
+
|
96
|
+
obj.__tracer_provider.add_span_processor(obj.__span_processor)
|
97
|
+
|
98
|
+
# This is not a real instrumentation and does not generate telemetry
|
99
|
+
# data, but it is required to ensure that OpenTelemetry context
|
100
|
+
# propagation is enabled.
|
101
|
+
# See the README at:
|
102
|
+
# https://pypi.org/project/opentelemetry-instrumentation-threading/
|
103
|
+
ThreadingInstrumentor().instrument()
|
104
|
+
|
105
|
+
init_instrumentations(
|
106
|
+
tracer_provider=obj.__tracer_provider,
|
107
|
+
instruments=instruments,
|
108
|
+
block_instruments=block_instruments,
|
109
|
+
client=obj.__client,
|
110
|
+
async_client=obj.__async_client,
|
111
|
+
)
|
112
|
+
|
113
|
+
# Force flushes for debug environments (e.g. local development)
|
114
|
+
atexit.register(obj.exit_handler)
|
115
|
+
|
116
|
+
return cls.instance
|
117
|
+
|
118
|
+
def exit_handler(self):
|
119
|
+
if isinstance(self.__span_processor, LaminarSpanProcessor):
|
120
|
+
self.__span_processor.clear()
|
121
|
+
self.flush()
|
122
|
+
|
123
|
+
def _initialize_logger(self):
|
124
|
+
self.__logger = logging.getLogger(__name__)
|
125
|
+
console_log_handler = logging.StreamHandler()
|
126
|
+
console_log_handler.setFormatter(VerboseColorfulFormatter())
|
127
|
+
self.__logger.addHandler(console_log_handler)
|
128
|
+
|
129
|
+
@staticmethod
|
130
|
+
def set_static_params(
|
131
|
+
resource_attributes: dict,
|
132
|
+
enable_content_tracing: bool,
|
133
|
+
) -> None:
|
134
|
+
TracerWrapper.resource_attributes = resource_attributes
|
135
|
+
TracerWrapper.enable_content_tracing = enable_content_tracing
|
136
|
+
|
137
|
+
@classmethod
|
138
|
+
def verify_initialized(cls) -> bool:
|
139
|
+
return hasattr(cls, "instance")
|
140
|
+
|
141
|
+
@classmethod
|
142
|
+
def clear(cls):
|
143
|
+
# Any state cleanup. Now used in between tests
|
144
|
+
if isinstance(cls.instance.__span_processor, LaminarSpanProcessor):
|
145
|
+
cls.instance.__span_processor.clear()
|
146
|
+
|
147
|
+
def shutdown(self):
|
148
|
+
if self.__tracer_provider is None:
|
149
|
+
return
|
150
|
+
self.__tracer_provider.shutdown()
|
151
|
+
|
152
|
+
def flush(self):
|
153
|
+
return self.__span_processor.force_flush()
|
154
|
+
|
155
|
+
def get_tracer(self):
|
156
|
+
if self.__tracer_provider is None:
|
157
|
+
return trace.get_tracer_provider().get_tracer(TRACER_NAME)
|
158
|
+
return self.__tracer_provider.get_tracer(TRACER_NAME)
|