lmnr 0.6.21__py3-none-any.whl → 0.7.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 +0 -4
- lmnr/opentelemetry_lib/decorators/__init__.py +81 -32
- lmnr/opentelemetry_lib/litellm/__init__.py +5 -2
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/__init__.py +6 -2
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py +11 -2
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/__init__.py +3 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py +16 -16
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/__init__.py +6 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/chat_wrappers.py +141 -9
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/completion_wrappers.py +10 -2
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +6 -2
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +8 -2
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py +4 -1
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/responses_wrappers.py +20 -4
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/threading/__init__.py +190 -0
- lmnr/opentelemetry_lib/tracing/__init__.py +89 -1
- lmnr/opentelemetry_lib/tracing/context.py +126 -0
- lmnr/opentelemetry_lib/tracing/processor.py +5 -6
- lmnr/opentelemetry_lib/tracing/tracer.py +29 -0
- lmnr/sdk/browser/browser_use_otel.py +5 -5
- lmnr/sdk/browser/patchright_otel.py +14 -0
- lmnr/sdk/browser/playwright_otel.py +32 -6
- lmnr/sdk/browser/pw_utils.py +119 -112
- lmnr/sdk/browser/recorder/record.umd.min.cjs +84 -0
- lmnr/sdk/client/asynchronous/resources/browser_events.py +1 -0
- lmnr/sdk/laminar.py +156 -186
- lmnr/sdk/types.py +17 -11
- lmnr/version.py +1 -1
- {lmnr-0.6.21.dist-info → lmnr-0.7.1.dist-info}/METADATA +3 -2
- {lmnr-0.6.21.dist-info → lmnr-0.7.1.dist-info}/RECORD +32 -31
- {lmnr-0.6.21.dist-info → lmnr-0.7.1.dist-info}/WHEEL +1 -1
- lmnr/opentelemetry_lib/tracing/context_properties.py +0 -65
- lmnr/sdk/browser/rrweb/rrweb.umd.min.cjs +0 -98
- {lmnr-0.6.21.dist-info → lmnr-0.7.1.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,8 @@
|
|
1
1
|
from contextlib import contextmanager
|
2
|
+
from typing import Generator, Tuple
|
2
3
|
|
3
4
|
from opentelemetry import trace
|
5
|
+
from opentelemetry.context import Context
|
4
6
|
from lmnr.opentelemetry_lib.tracing import TracerWrapper
|
5
7
|
|
6
8
|
|
@@ -16,3 +18,30 @@ def get_tracer(flush_on_exit: bool = False):
|
|
16
18
|
finally:
|
17
19
|
if flush_on_exit:
|
18
20
|
wrapper.flush()
|
21
|
+
|
22
|
+
|
23
|
+
@contextmanager
|
24
|
+
def get_tracer_with_context(
|
25
|
+
flush_on_exit: bool = False,
|
26
|
+
) -> Generator[Tuple[trace.Tracer, Context], None, None]:
|
27
|
+
"""Get tracer with isolated context. Returns (tracer, context) tuple."""
|
28
|
+
wrapper = TracerWrapper()
|
29
|
+
try:
|
30
|
+
tracer = wrapper.get_tracer()
|
31
|
+
context = wrapper.get_isolated_context()
|
32
|
+
yield tracer, context
|
33
|
+
finally:
|
34
|
+
if flush_on_exit:
|
35
|
+
wrapper.flush()
|
36
|
+
|
37
|
+
|
38
|
+
def copy_current_context() -> Context:
|
39
|
+
"""Copy the current isolated context for use in threads/tasks."""
|
40
|
+
wrapper = TracerWrapper()
|
41
|
+
return wrapper.get_isolated_context()
|
42
|
+
|
43
|
+
|
44
|
+
def set_context_for_thread(context: Context) -> None:
|
45
|
+
"""Set the isolated context for the current thread."""
|
46
|
+
wrapper = TracerWrapper()
|
47
|
+
wrapper.set_isolated_context(context)
|
@@ -28,8 +28,8 @@ WRAPPED_METHODS = [
|
|
28
28
|
"object": "Agent",
|
29
29
|
"method": "run",
|
30
30
|
"span_name": "agent.run",
|
31
|
-
"ignore_input":
|
32
|
-
"ignore_output":
|
31
|
+
"ignore_input": True,
|
32
|
+
"ignore_output": True,
|
33
33
|
"span_type": "DEFAULT",
|
34
34
|
},
|
35
35
|
{
|
@@ -47,15 +47,15 @@ WRAPPED_METHODS = [
|
|
47
47
|
"method": "act",
|
48
48
|
"span_name": "controller.act",
|
49
49
|
"ignore_input": True,
|
50
|
-
"ignore_output":
|
50
|
+
"ignore_output": True,
|
51
51
|
"span_type": "DEFAULT",
|
52
52
|
},
|
53
53
|
{
|
54
54
|
"package": "browser_use.controller.registry.service",
|
55
55
|
"object": "Registry",
|
56
56
|
"method": "execute_action",
|
57
|
-
"ignore_input":
|
58
|
-
"ignore_output":
|
57
|
+
"ignore_input": True,
|
58
|
+
"ignore_output": True,
|
59
59
|
"span_type": "TOOL",
|
60
60
|
},
|
61
61
|
]
|
@@ -1,4 +1,6 @@
|
|
1
1
|
from lmnr.sdk.browser.playwright_otel import (
|
2
|
+
_wrap_bring_to_front_async,
|
3
|
+
_wrap_bring_to_front_sync,
|
2
4
|
_wrap_new_browser_sync,
|
3
5
|
_wrap_new_browser_async,
|
4
6
|
_wrap_new_context_sync,
|
@@ -46,6 +48,12 @@ WRAPPED_METHODS = [
|
|
46
48
|
"method": "launch_persistent_context",
|
47
49
|
"wrapper": _wrap_new_context_sync,
|
48
50
|
},
|
51
|
+
{
|
52
|
+
"package": "patchright.sync_api",
|
53
|
+
"object": "Page",
|
54
|
+
"method": "bring_to_front",
|
55
|
+
"wrapper": _wrap_bring_to_front_sync,
|
56
|
+
},
|
49
57
|
]
|
50
58
|
|
51
59
|
WRAPPED_METHODS_ASYNC = [
|
@@ -79,6 +87,12 @@ WRAPPED_METHODS_ASYNC = [
|
|
79
87
|
"method": "launch_persistent_context",
|
80
88
|
"wrapper": _wrap_new_context_async,
|
81
89
|
},
|
90
|
+
{
|
91
|
+
"package": "patchright.async_api",
|
92
|
+
"object": "Page",
|
93
|
+
"method": "bring_to_front",
|
94
|
+
"wrapper": _wrap_bring_to_front_async,
|
95
|
+
},
|
82
96
|
]
|
83
97
|
|
84
98
|
|
@@ -60,8 +60,15 @@ def _wrap_new_browser_sync(
|
|
60
60
|
browser: SyncBrowser = wrapped(*args, **kwargs)
|
61
61
|
session_id = str(uuid.uuid4().hex)
|
62
62
|
|
63
|
+
def create_page_handler(session_id, client):
|
64
|
+
def page_handler(page):
|
65
|
+
start_recording_events_sync(page, session_id, client)
|
66
|
+
|
67
|
+
return page_handler
|
68
|
+
|
63
69
|
for context in browser.contexts:
|
64
|
-
|
70
|
+
page_handler = create_page_handler(session_id, client)
|
71
|
+
context.on("page", page_handler)
|
65
72
|
for page in context.pages:
|
66
73
|
start_recording_events_sync(page, session_id, client)
|
67
74
|
|
@@ -75,10 +82,15 @@ async def _wrap_new_browser_async(
|
|
75
82
|
browser: Browser = await wrapped(*args, **kwargs)
|
76
83
|
session_id = str(uuid.uuid4().hex)
|
77
84
|
|
85
|
+
def create_page_handler(session_id, client):
|
86
|
+
async def page_handler(page):
|
87
|
+
await start_recording_events_async(page, session_id, client)
|
88
|
+
|
89
|
+
return page_handler
|
90
|
+
|
78
91
|
for context in browser.contexts:
|
79
|
-
|
80
|
-
|
81
|
-
)
|
92
|
+
page_handler = create_page_handler(session_id, client)
|
93
|
+
context.on("page", page_handler)
|
82
94
|
for page in context.pages:
|
83
95
|
await start_recording_events_async(page, session_id, client)
|
84
96
|
return browser
|
@@ -91,7 +103,14 @@ def _wrap_new_context_sync(
|
|
91
103
|
context: SyncBrowserContext = wrapped(*args, **kwargs)
|
92
104
|
session_id = str(uuid.uuid4().hex)
|
93
105
|
|
94
|
-
|
106
|
+
def create_page_handler(session_id, client):
|
107
|
+
def page_handler(page):
|
108
|
+
start_recording_events_sync(page, session_id, client)
|
109
|
+
|
110
|
+
return page_handler
|
111
|
+
|
112
|
+
page_handler = create_page_handler(session_id, client)
|
113
|
+
context.on("page", page_handler)
|
95
114
|
for page in context.pages:
|
96
115
|
start_recording_events_sync(page, session_id, client)
|
97
116
|
|
@@ -105,7 +124,14 @@ async def _wrap_new_context_async(
|
|
105
124
|
context: BrowserContext = await wrapped(*args, **kwargs)
|
106
125
|
session_id = str(uuid.uuid4().hex)
|
107
126
|
|
108
|
-
|
127
|
+
def create_page_handler(session_id, client):
|
128
|
+
async def page_handler(page):
|
129
|
+
await start_recording_events_async(page, session_id, client)
|
130
|
+
|
131
|
+
return page_handler
|
132
|
+
|
133
|
+
page_handler = create_page_handler(session_id, client)
|
134
|
+
context.on("page", page_handler)
|
109
135
|
for page in context.pages:
|
110
136
|
await start_recording_events_async(page, session_id, client)
|
111
137
|
|
lmnr/sdk/browser/pw_utils.py
CHANGED
@@ -8,6 +8,7 @@ from lmnr.sdk.decorators import observe
|
|
8
8
|
from lmnr.sdk.browser.utils import retry_sync, retry_async
|
9
9
|
from lmnr.sdk.client.synchronous.sync_client import LaminarClient
|
10
10
|
from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
|
11
|
+
from lmnr.opentelemetry_lib.tracing.context import get_current_context
|
11
12
|
|
12
13
|
try:
|
13
14
|
if is_package_installed("playwright"):
|
@@ -32,13 +33,15 @@ except ImportError as e:
|
|
32
33
|
logger = logging.getLogger(__name__)
|
33
34
|
|
34
35
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
35
|
-
with open(os.path.join(current_dir, "
|
36
|
+
with open(os.path.join(current_dir, "recorder", "record.umd.min.cjs"), "r") as f:
|
36
37
|
RRWEB_CONTENT = f"() => {{ {f.read()} }}"
|
37
38
|
|
38
39
|
INJECT_PLACEHOLDER = """
|
39
40
|
() => {
|
40
41
|
const BATCH_TIMEOUT = 2000; // Send events after 2 seconds
|
41
|
-
|
42
|
+
const MAX_WORKER_PROMISES = 50; // Max concurrent worker promises
|
43
|
+
const HEARTBEAT_INTERVAL = 1000;
|
44
|
+
|
42
45
|
window.lmnrRrwebEventsBatch = [];
|
43
46
|
|
44
47
|
// Create a Web Worker for heavy JSON processing with chunked processing
|
@@ -97,6 +100,29 @@ INJECT_PLACEHOLDER = """
|
|
97
100
|
let workerPromises = new Map();
|
98
101
|
let workerId = 0;
|
99
102
|
|
103
|
+
// Cleanup function for worker
|
104
|
+
const cleanupWorker = () => {
|
105
|
+
if (compressionWorker) {
|
106
|
+
compressionWorker.terminate();
|
107
|
+
compressionWorker = null;
|
108
|
+
}
|
109
|
+
workerPromises.clear();
|
110
|
+
workerId = 0;
|
111
|
+
};
|
112
|
+
|
113
|
+
// Clean up stale promises to prevent memory leaks
|
114
|
+
const cleanupStalePromises = () => {
|
115
|
+
if (workerPromises.size > MAX_WORKER_PROMISES) {
|
116
|
+
const toDelete = [];
|
117
|
+
for (const [id, promise] of workerPromises) {
|
118
|
+
if (toDelete.length >= workerPromises.size - MAX_WORKER_PROMISES) break;
|
119
|
+
toDelete.push(id);
|
120
|
+
promise.reject(new Error('Promise cleaned up due to memory pressure'));
|
121
|
+
}
|
122
|
+
toDelete.forEach(id => workerPromises.delete(id));
|
123
|
+
}
|
124
|
+
};
|
125
|
+
|
100
126
|
// Non-blocking JSON.stringify using chunked processing
|
101
127
|
function stringifyNonBlocking(obj, chunkSize = 10000) {
|
102
128
|
return new Promise((resolve, reject) => {
|
@@ -196,6 +222,9 @@ INJECT_PLACEHOLDER = """
|
|
196
222
|
// Alternative: Use transferable objects for maximum efficiency
|
197
223
|
async function compressLargeObjectTransferable(data) {
|
198
224
|
try {
|
225
|
+
// Clean up stale promises first
|
226
|
+
cleanupStalePromises();
|
227
|
+
|
199
228
|
// Stringify on main thread but non-blocking
|
200
229
|
const jsonString = await stringifyNonBlocking(data);
|
201
230
|
|
@@ -219,11 +248,24 @@ INJECT_PLACEHOLDER = """
|
|
219
248
|
}
|
220
249
|
}
|
221
250
|
};
|
251
|
+
|
252
|
+
compressionWorker.onerror = (error) => {
|
253
|
+
console.error('Compression worker error:', error);
|
254
|
+
cleanupWorker();
|
255
|
+
};
|
222
256
|
}
|
223
257
|
|
224
258
|
const id = ++workerId;
|
225
259
|
workerPromises.set(id, { resolve, reject });
|
226
260
|
|
261
|
+
// Set timeout to prevent hanging promises
|
262
|
+
setTimeout(() => {
|
263
|
+
if (workerPromises.has(id)) {
|
264
|
+
workerPromises.delete(id);
|
265
|
+
reject(new Error('Compression timeout'));
|
266
|
+
}
|
267
|
+
}, 10000);
|
268
|
+
|
227
269
|
// Transfer the ArrayBuffer (no copying!)
|
228
270
|
compressionWorker.postMessage({
|
229
271
|
buffer,
|
@@ -262,15 +304,32 @@ INJECT_PLACEHOLDER = """
|
|
262
304
|
}
|
263
305
|
}
|
264
306
|
};
|
307
|
+
|
308
|
+
compressionWorker.onerror = (error) => {
|
309
|
+
console.error('Compression worker error:', error);
|
310
|
+
cleanupWorker();
|
311
|
+
};
|
265
312
|
}
|
266
313
|
|
267
314
|
const id = ++workerId;
|
268
315
|
workerPromises.set(id, { resolve, reject });
|
316
|
+
|
317
|
+
// Set timeout to prevent hanging promises
|
318
|
+
setTimeout(() => {
|
319
|
+
if (workerPromises.has(id)) {
|
320
|
+
workerPromises.delete(id);
|
321
|
+
reject(new Error('Compression timeout'));
|
322
|
+
}
|
323
|
+
}, 10000);
|
324
|
+
|
269
325
|
compressionWorker.postMessage({ jsonString, id });
|
270
326
|
});
|
271
327
|
}
|
272
328
|
}
|
273
329
|
|
330
|
+
|
331
|
+
setInterval(cleanupWorker, 5000);
|
332
|
+
|
274
333
|
function isLargeEvent(type) {
|
275
334
|
const LARGE_EVENT_TYPES = [
|
276
335
|
2, // FullSnapshot
|
@@ -299,14 +358,15 @@ INJECT_PLACEHOLDER = """
|
|
299
358
|
|
300
359
|
setInterval(sendBatchIfReady, BATCH_TIMEOUT);
|
301
360
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
})
|
308
|
-
|
309
|
-
|
361
|
+
async function bufferToBase64(buffer) {
|
362
|
+
const base64url = await new Promise(r => {
|
363
|
+
const reader = new FileReader()
|
364
|
+
reader.onload = () => r(reader.result)
|
365
|
+
reader.readAsDataURL(new Blob([buffer]))
|
366
|
+
});
|
367
|
+
return base64url.slice(base64url.indexOf(',') + 1);
|
368
|
+
}
|
369
|
+
|
310
370
|
window.lmnrRrweb.record({
|
311
371
|
async emit(event) {
|
312
372
|
try {
|
@@ -315,9 +375,10 @@ INJECT_PLACEHOLDER = """
|
|
315
375
|
await compressLargeObject(event.data, true) :
|
316
376
|
await compressSmallObject(event.data);
|
317
377
|
|
378
|
+
const base64Data = await bufferToBase64(compressedResult);
|
318
379
|
const eventToSend = {
|
319
380
|
...event,
|
320
|
-
data:
|
381
|
+
data: base64Data,
|
321
382
|
};
|
322
383
|
window.lmnrRrwebEventsBatch.push(eventToSend);
|
323
384
|
} catch (error) {
|
@@ -328,63 +389,22 @@ INJECT_PLACEHOLDER = """
|
|
328
389
|
collectFonts: true,
|
329
390
|
recordCrossOriginIframes: true
|
330
391
|
});
|
331
|
-
}
|
332
|
-
"""
|
333
|
-
|
334
392
|
|
335
|
-
|
336
|
-
|
337
|
-
)
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
return [];
|
346
|
-
}
|
347
|
-
return window.lmnrGetAndClearEvents();
|
348
|
-
}
|
349
|
-
"""
|
350
|
-
)
|
351
|
-
|
352
|
-
if not events or len(events) == 0:
|
353
|
-
return
|
354
|
-
|
355
|
-
await client._browser_events.send(session_id, trace_id, events)
|
356
|
-
except Exception as e:
|
357
|
-
if "Page.evaluate: Target page, context or browser has been closed" not in str(
|
358
|
-
e
|
359
|
-
):
|
360
|
-
logger.debug(f"Could not send events: {e}")
|
361
|
-
|
362
|
-
|
363
|
-
def send_events_sync(
|
364
|
-
page: SyncPage, session_id: str, trace_id: str, client: LaminarClient
|
365
|
-
):
|
366
|
-
"""Synchronous version of send_events"""
|
367
|
-
try:
|
368
|
-
events = page.evaluate(
|
369
|
-
"""
|
370
|
-
() => {
|
371
|
-
if (typeof window.lmnrGetAndClearEvents !== 'function') {
|
372
|
-
return [];
|
373
|
-
}
|
374
|
-
return window.lmnrGetAndClearEvents();
|
375
|
-
}
|
376
|
-
"""
|
377
|
-
)
|
378
|
-
if not events or len(events) == 0:
|
379
|
-
return
|
393
|
+
function heartbeat() {
|
394
|
+
// Add heartbeat events
|
395
|
+
setInterval(() => {
|
396
|
+
window.lmnrRrweb.record.addCustomEvent('heartbeat', {
|
397
|
+
title: document.title,
|
398
|
+
url: document.URL,
|
399
|
+
})
|
400
|
+
}, HEARTBEAT_INTERVAL
|
401
|
+
);
|
402
|
+
}
|
380
403
|
|
381
|
-
|
404
|
+
heartbeat();
|
382
405
|
|
383
|
-
|
384
|
-
|
385
|
-
e
|
386
|
-
):
|
387
|
-
logger.debug(f"Could not send events: {e}")
|
406
|
+
}
|
407
|
+
"""
|
388
408
|
|
389
409
|
|
390
410
|
def inject_session_recorder_sync(page: SyncPage):
|
@@ -414,10 +434,10 @@ def inject_session_recorder_sync(page: SyncPage):
|
|
414
434
|
):
|
415
435
|
return
|
416
436
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
437
|
+
try:
|
438
|
+
page.evaluate(INJECT_PLACEHOLDER)
|
439
|
+
except Exception as e:
|
440
|
+
logger.debug(f"Failed to inject session recorder: {e}")
|
421
441
|
|
422
442
|
except Exception as e:
|
423
443
|
logger.error(f"Error during session recorder injection: {e}")
|
@@ -450,10 +470,10 @@ async def inject_session_recorder_async(page: Page):
|
|
450
470
|
):
|
451
471
|
return
|
452
472
|
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
473
|
+
try:
|
474
|
+
await page.evaluate(INJECT_PLACEHOLDER)
|
475
|
+
except Exception as e:
|
476
|
+
logger.debug(f"Failed to inject session recorder placeholder: {e}")
|
457
477
|
|
458
478
|
except Exception as e:
|
459
479
|
logger.error(f"Error during session recorder injection: {e}")
|
@@ -461,7 +481,9 @@ async def inject_session_recorder_async(page: Page):
|
|
461
481
|
|
462
482
|
@observe(name="playwright.page", ignore_input=True, ignore_output=True)
|
463
483
|
def start_recording_events_sync(page: SyncPage, session_id: str, client: LaminarClient):
|
464
|
-
|
484
|
+
|
485
|
+
ctx = get_current_context()
|
486
|
+
span = trace.get_current_span(ctx)
|
465
487
|
trace_id = format(span.get_span_context().trace_id, "032x")
|
466
488
|
span.set_attribute("lmnr.internal.has_browser_session", True)
|
467
489
|
|
@@ -471,24 +493,6 @@ def start_recording_events_sync(page: SyncPage, session_id: str, client: Laminar
|
|
471
493
|
except Exception:
|
472
494
|
pass
|
473
495
|
|
474
|
-
def on_load():
|
475
|
-
try:
|
476
|
-
inject_session_recorder_sync(page)
|
477
|
-
except Exception as e:
|
478
|
-
logger.error(f"Error in on_load handler: {e}")
|
479
|
-
|
480
|
-
def on_close():
|
481
|
-
try:
|
482
|
-
send_events_sync(page, session_id, trace_id, client)
|
483
|
-
except Exception:
|
484
|
-
pass
|
485
|
-
|
486
|
-
page.on("load", on_load)
|
487
|
-
page.on("close", on_close)
|
488
|
-
|
489
|
-
inject_session_recorder_sync(page)
|
490
|
-
|
491
|
-
# Expose function to browser so it can call us when events are ready
|
492
496
|
def send_events_from_browser(events):
|
493
497
|
try:
|
494
498
|
if events and len(events) > 0:
|
@@ -501,12 +505,23 @@ def start_recording_events_sync(page: SyncPage, session_id: str, client: Laminar
|
|
501
505
|
except Exception as e:
|
502
506
|
logger.debug(f"Could not expose function: {e}")
|
503
507
|
|
508
|
+
inject_session_recorder_sync(page)
|
509
|
+
|
510
|
+
def on_load(p):
|
511
|
+
try:
|
512
|
+
inject_session_recorder_sync(p)
|
513
|
+
except Exception as e:
|
514
|
+
logger.error(f"Error in on_load handler: {e}")
|
515
|
+
|
516
|
+
page.on("domcontentloaded", on_load)
|
517
|
+
|
504
518
|
|
505
519
|
@observe(name="playwright.page", ignore_input=True, ignore_output=True)
|
506
520
|
async def start_recording_events_async(
|
507
521
|
page: Page, session_id: str, client: AsyncLaminarClient
|
508
522
|
):
|
509
|
-
|
523
|
+
ctx = get_current_context()
|
524
|
+
span = trace.get_current_span(ctx)
|
510
525
|
trace_id = format(span.get_span_context().trace_id, "032x")
|
511
526
|
span.set_attribute("lmnr.internal.has_browser_session", True)
|
512
527
|
|
@@ -517,25 +532,7 @@ async def start_recording_events_async(
|
|
517
532
|
return
|
518
533
|
except Exception:
|
519
534
|
pass
|
520
|
-
|
521
|
-
async def on_load(p):
|
522
|
-
try:
|
523
|
-
await inject_session_recorder_async(p)
|
524
|
-
except Exception as e:
|
525
|
-
logger.error(f"Error in on_load handler: {e}")
|
526
|
-
|
527
|
-
async def on_close(p):
|
528
|
-
try:
|
529
|
-
# Send any remaining events before closing
|
530
|
-
await send_events_async(p, session_id, trace_id, client)
|
531
|
-
except Exception:
|
532
|
-
pass
|
533
|
-
|
534
|
-
page.on("load", on_load)
|
535
|
-
page.on("close", on_close)
|
536
|
-
|
537
|
-
await inject_session_recorder_async(page)
|
538
|
-
|
535
|
+
|
539
536
|
async def send_events_from_browser(events):
|
540
537
|
try:
|
541
538
|
if events and len(events) > 0:
|
@@ -548,6 +545,16 @@ async def start_recording_events_async(
|
|
548
545
|
except Exception as e:
|
549
546
|
logger.debug(f"Could not expose function: {e}")
|
550
547
|
|
548
|
+
await inject_session_recorder_async(page)
|
549
|
+
|
550
|
+
async def on_load(p):
|
551
|
+
try:
|
552
|
+
await inject_session_recorder_async(p)
|
553
|
+
except Exception as e:
|
554
|
+
logger.error(f"Error in on_load handler: {e}")
|
555
|
+
|
556
|
+
page.on("domcontentloaded", on_load)
|
557
|
+
|
551
558
|
|
552
559
|
def take_full_snapshot(page: Page):
|
553
560
|
return page.evaluate(
|