plato-sdk-v2 2.3.3__py3-none-any.whl → 2.3.5__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.
- plato/agents/__init__.py +24 -16
- plato/agents/artifacts.py +108 -0
- plato/agents/config.py +2 -7
- plato/agents/otel.py +258 -0
- plato/agents/runner.py +223 -68
- plato/worlds/README.md +2 -1
- plato/worlds/base.py +111 -78
- plato/worlds/config.py +5 -3
- plato/worlds/runner.py +62 -18
- {plato_sdk_v2-2.3.3.dist-info → plato_sdk_v2-2.3.5.dist-info}/METADATA +4 -2
- {plato_sdk_v2-2.3.3.dist-info → plato_sdk_v2-2.3.5.dist-info}/RECORD +13 -18
- plato/agents/logging.py +0 -515
- plato/chronos/api/callback/__init__.py +0 -11
- plato/chronos/api/callback/push_agent_logs.py +0 -61
- plato/chronos/api/callback/update_agent_status.py +0 -57
- plato/chronos/api/callback/upload_artifacts.py +0 -59
- plato/chronos/api/callback/upload_logs_zip.py +0 -57
- plato/chronos/api/callback/upload_trajectory.py +0 -57
- {plato_sdk_v2-2.3.3.dist-info → plato_sdk_v2-2.3.5.dist-info}/WHEEL +0 -0
- {plato_sdk_v2-2.3.3.dist-info → plato_sdk_v2-2.3.5.dist-info}/entry_points.txt +0 -0
plato/agents/__init__.py
CHANGED
|
@@ -20,8 +20,9 @@ Trajectory (ATIF):
|
|
|
20
20
|
- Trajectory: ATIF trajectory model
|
|
21
21
|
- Step, Agent, ToolCall, etc.: ATIF components
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
-
|
|
23
|
+
OTel Tracing:
|
|
24
|
+
- instrument: Initialize OTel tracing from environment
|
|
25
|
+
- get_tracer: Get a tracer for creating spans
|
|
25
26
|
|
|
26
27
|
Example (direct execution):
|
|
27
28
|
from plato.agents import BaseAgent, AgentConfig, Secret, register_agent
|
|
@@ -80,16 +81,25 @@ __all__ = [
|
|
|
80
81
|
"Metrics",
|
|
81
82
|
"FinalMetrics",
|
|
82
83
|
"SCHEMA_VERSION",
|
|
83
|
-
#
|
|
84
|
-
"
|
|
85
|
-
"span",
|
|
86
|
-
"log_event",
|
|
84
|
+
# Artifacts
|
|
85
|
+
"zip_directory",
|
|
87
86
|
"upload_artifacts",
|
|
88
87
|
"upload_artifact",
|
|
89
|
-
"
|
|
90
|
-
|
|
88
|
+
"upload_to_s3",
|
|
89
|
+
# OTel tracing
|
|
90
|
+
"init_tracing",
|
|
91
|
+
"instrument",
|
|
92
|
+
"shutdown_tracing",
|
|
93
|
+
"get_tracer",
|
|
94
|
+
"is_initialized",
|
|
91
95
|
]
|
|
92
96
|
|
|
97
|
+
from plato.agents.artifacts import (
|
|
98
|
+
upload_artifact,
|
|
99
|
+
upload_artifacts,
|
|
100
|
+
upload_to_s3,
|
|
101
|
+
zip_directory,
|
|
102
|
+
)
|
|
93
103
|
from plato.agents.base import (
|
|
94
104
|
BaseAgent,
|
|
95
105
|
ConfigT,
|
|
@@ -99,14 +109,12 @@ from plato.agents.base import (
|
|
|
99
109
|
)
|
|
100
110
|
from plato.agents.build import BuildConfig, load_build_config
|
|
101
111
|
from plato.agents.config import AgentConfig, Secret
|
|
102
|
-
from plato.agents.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
upload_artifacts,
|
|
109
|
-
upload_checkpoint,
|
|
112
|
+
from plato.agents.otel import (
|
|
113
|
+
get_tracer,
|
|
114
|
+
init_tracing,
|
|
115
|
+
instrument,
|
|
116
|
+
is_initialized,
|
|
117
|
+
shutdown_tracing,
|
|
110
118
|
)
|
|
111
119
|
from plato.agents.runner import run_agent
|
|
112
120
|
from plato.agents.trajectory import (
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Artifact upload utilities for Plato agents and worlds.
|
|
2
|
+
|
|
3
|
+
These functions upload artifacts directly to S3 using presigned URLs.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import io
|
|
9
|
+
import logging
|
|
10
|
+
import zipfile
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def zip_directory(dir_path: str) -> bytes:
|
|
19
|
+
"""Zip an entire directory.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
dir_path: Path to the directory
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Zip file contents as bytes.
|
|
26
|
+
"""
|
|
27
|
+
path = Path(dir_path)
|
|
28
|
+
buffer = io.BytesIO()
|
|
29
|
+
|
|
30
|
+
with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
31
|
+
for file_path in path.rglob("*"):
|
|
32
|
+
if file_path.is_file():
|
|
33
|
+
arcname = file_path.relative_to(path)
|
|
34
|
+
zf.write(file_path, arcname)
|
|
35
|
+
|
|
36
|
+
buffer.seek(0)
|
|
37
|
+
return buffer.read()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def upload_to_s3(upload_url: str, data: bytes, content_type: str = "application/octet-stream") -> bool:
|
|
41
|
+
"""Upload data directly to S3 using a presigned URL.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
upload_url: Presigned S3 PUT URL
|
|
45
|
+
data: Raw bytes to upload
|
|
46
|
+
content_type: MIME type of the content
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
True if successful, False otherwise
|
|
50
|
+
"""
|
|
51
|
+
if not upload_url:
|
|
52
|
+
logger.warning("No upload URL provided")
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
async with httpx.AsyncClient(timeout=120.0) as client:
|
|
57
|
+
response = await client.put(
|
|
58
|
+
upload_url,
|
|
59
|
+
content=data,
|
|
60
|
+
headers={"Content-Type": content_type},
|
|
61
|
+
)
|
|
62
|
+
if response.status_code in (200, 201, 204):
|
|
63
|
+
logger.info(f"Uploaded {len(data)} bytes to S3")
|
|
64
|
+
return True
|
|
65
|
+
else:
|
|
66
|
+
logger.warning(f"S3 upload failed: {response.status_code} {response.text}")
|
|
67
|
+
return False
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.warning(f"Failed to upload to S3: {e}")
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def upload_artifacts(upload_url: str, dir_path: str) -> bool:
|
|
74
|
+
"""Upload a directory as a zip directly to S3.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
upload_url: Presigned S3 PUT URL
|
|
78
|
+
dir_path: Path to the directory to upload
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
True if successful, False otherwise
|
|
82
|
+
"""
|
|
83
|
+
try:
|
|
84
|
+
zip_data = zip_directory(dir_path)
|
|
85
|
+
logger.info(f"Zipped directory: {len(zip_data)} bytes")
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.warning(f"Failed to zip directory: {e}")
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
return await upload_to_s3(upload_url, zip_data, "application/zip")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async def upload_artifact(
|
|
94
|
+
upload_url: str,
|
|
95
|
+
data: bytes,
|
|
96
|
+
content_type: str = "application/octet-stream",
|
|
97
|
+
) -> bool:
|
|
98
|
+
"""Upload an artifact directly to S3.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
upload_url: Presigned S3 PUT URL
|
|
102
|
+
data: Raw bytes of the artifact
|
|
103
|
+
content_type: MIME type of the content
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True if successful, False otherwise
|
|
107
|
+
"""
|
|
108
|
+
return await upload_to_s3(upload_url, data, content_type)
|
plato/agents/config.py
CHANGED
|
@@ -21,7 +21,6 @@ import json
|
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from typing import Any
|
|
23
23
|
|
|
24
|
-
from pydantic import Field
|
|
25
24
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
26
25
|
|
|
27
26
|
|
|
@@ -57,8 +56,6 @@ class AgentConfig(BaseSettings):
|
|
|
57
56
|
|
|
58
57
|
Attributes:
|
|
59
58
|
logs_dir: Directory for agent logs and trajectory output.
|
|
60
|
-
checkpoint_paths: Directories to watch for checkpoint triggers (for workspace tracking).
|
|
61
|
-
checkpoint_debounce_ms: Debounce interval for checkpoints.
|
|
62
59
|
"""
|
|
63
60
|
|
|
64
61
|
model_config = SettingsConfigDict(
|
|
@@ -68,8 +65,6 @@ class AgentConfig(BaseSettings):
|
|
|
68
65
|
)
|
|
69
66
|
|
|
70
67
|
logs_dir: str = "/logs"
|
|
71
|
-
checkpoint_paths: list[str] = Field(default_factory=list)
|
|
72
|
-
checkpoint_debounce_ms: int = 500
|
|
73
68
|
|
|
74
69
|
@classmethod
|
|
75
70
|
def get_field_secrets(cls) -> dict[str, Secret]:
|
|
@@ -97,7 +92,7 @@ class AgentConfig(BaseSettings):
|
|
|
97
92
|
secrets = []
|
|
98
93
|
|
|
99
94
|
# Skip internal fields
|
|
100
|
-
internal_fields = {"logs_dir"
|
|
95
|
+
internal_fields = {"logs_dir"}
|
|
101
96
|
|
|
102
97
|
for field_name, prop_schema in properties.items():
|
|
103
98
|
if field_name in internal_fields:
|
|
@@ -140,7 +135,7 @@ class AgentConfig(BaseSettings):
|
|
|
140
135
|
def get_config_dict(self) -> dict[str, Any]:
|
|
141
136
|
"""Extract non-secret config values as a dict."""
|
|
142
137
|
secrets_map = self.get_field_secrets()
|
|
143
|
-
internal_fields = {"logs_dir"
|
|
138
|
+
internal_fields = {"logs_dir"}
|
|
144
139
|
|
|
145
140
|
result: dict[str, Any] = {}
|
|
146
141
|
for field_name in self.model_fields:
|
plato/agents/otel.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""OpenTelemetry integration for Plato agents and worlds.
|
|
2
|
+
|
|
3
|
+
Provides tracing and logging utilities using OpenTelemetry SDK. Traces and logs
|
|
4
|
+
are sent directly to the Chronos OTLP endpoint.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from plato.agents.otel import init_tracing, get_tracer, shutdown_tracing
|
|
8
|
+
|
|
9
|
+
# Initialize tracing (sends to Chronos OTLP endpoint)
|
|
10
|
+
init_tracing(
|
|
11
|
+
service_name="my-world",
|
|
12
|
+
session_id="session-123",
|
|
13
|
+
otlp_endpoint="http://chronos/api/otel",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Create spans
|
|
17
|
+
tracer = get_tracer()
|
|
18
|
+
with tracer.start_as_current_span("my-operation") as span:
|
|
19
|
+
span.set_attribute("key", "value")
|
|
20
|
+
# ... do work ...
|
|
21
|
+
|
|
22
|
+
# All Python logging is automatically sent to Chronos
|
|
23
|
+
import logging
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
logger.info("This will appear in the trajectory viewer!")
|
|
26
|
+
|
|
27
|
+
# Cleanup
|
|
28
|
+
shutdown_tracing()
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import logging
|
|
34
|
+
|
|
35
|
+
from opentelemetry import trace
|
|
36
|
+
from opentelemetry.trace import Tracer
|
|
37
|
+
|
|
38
|
+
_module_logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
# Global state
|
|
41
|
+
_tracer_provider = None
|
|
42
|
+
_logging_handler = None
|
|
43
|
+
_initialized = False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class OTelLoggingHandler(logging.Handler):
|
|
47
|
+
"""Logging handler that emits OTel spans for log messages.
|
|
48
|
+
|
|
49
|
+
Each log message becomes a span with:
|
|
50
|
+
- span.type: "log"
|
|
51
|
+
- log.level: DEBUG/INFO/WARNING/ERROR/CRITICAL
|
|
52
|
+
- content: the log message
|
|
53
|
+
- source: the logger name
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, tracer_name: str = "plato.logging"):
|
|
57
|
+
super().__init__()
|
|
58
|
+
self._tracer_name = tracer_name
|
|
59
|
+
# Filter out noisy loggers
|
|
60
|
+
self._ignored_loggers = {
|
|
61
|
+
"httpx",
|
|
62
|
+
"httpcore",
|
|
63
|
+
"urllib3",
|
|
64
|
+
"asyncio",
|
|
65
|
+
"opentelemetry",
|
|
66
|
+
"plato.agents.otel", # Avoid recursion
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
70
|
+
"""Emit a log record as an OTel span."""
|
|
71
|
+
# Skip ignored loggers
|
|
72
|
+
logger_name = record.name
|
|
73
|
+
for ignored in self._ignored_loggers:
|
|
74
|
+
if logger_name.startswith(ignored):
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
tracer = trace.get_tracer(self._tracer_name)
|
|
79
|
+
|
|
80
|
+
# Format the message
|
|
81
|
+
try:
|
|
82
|
+
msg = self.format(record)
|
|
83
|
+
except Exception:
|
|
84
|
+
msg = record.getMessage()
|
|
85
|
+
|
|
86
|
+
# Create a span for the log message
|
|
87
|
+
with tracer.start_as_current_span(
|
|
88
|
+
f"log.{record.levelname.lower()}",
|
|
89
|
+
end_on_exit=True,
|
|
90
|
+
) as span:
|
|
91
|
+
span.set_attribute("span.type", "log")
|
|
92
|
+
span.set_attribute("log.level", record.levelname)
|
|
93
|
+
span.set_attribute("content", msg)
|
|
94
|
+
span.set_attribute("source", logger_name)
|
|
95
|
+
|
|
96
|
+
# Add extra context if available
|
|
97
|
+
if record.funcName:
|
|
98
|
+
span.set_attribute("log.function", record.funcName)
|
|
99
|
+
if record.pathname:
|
|
100
|
+
span.set_attribute("log.file", record.pathname)
|
|
101
|
+
if record.lineno:
|
|
102
|
+
span.set_attribute("log.line", record.lineno)
|
|
103
|
+
|
|
104
|
+
# If there's an exception, record it
|
|
105
|
+
if record.exc_info and record.exc_info[1]:
|
|
106
|
+
span.record_exception(record.exc_info[1])
|
|
107
|
+
|
|
108
|
+
except Exception:
|
|
109
|
+
# Don't let logging failures break the application
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def init_tracing(
|
|
114
|
+
service_name: str,
|
|
115
|
+
session_id: str,
|
|
116
|
+
otlp_endpoint: str,
|
|
117
|
+
capture_logging: bool = True,
|
|
118
|
+
log_level: int = logging.INFO,
|
|
119
|
+
) -> None:
|
|
120
|
+
"""Initialize OpenTelemetry tracing and optionally capture Python logging.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
service_name: Name of the service (e.g., world name or agent name)
|
|
124
|
+
session_id: Chronos session ID (added as resource attribute)
|
|
125
|
+
otlp_endpoint: Chronos OTLP endpoint (e.g., http://chronos/api/otel)
|
|
126
|
+
capture_logging: If True, install handler to capture Python logs as OTel spans
|
|
127
|
+
log_level: Minimum log level to capture (default: INFO)
|
|
128
|
+
"""
|
|
129
|
+
global _tracer_provider, _logging_handler, _initialized
|
|
130
|
+
|
|
131
|
+
if _initialized:
|
|
132
|
+
_module_logger.debug("Tracing already initialized")
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
|
|
137
|
+
OTLPSpanExporter,
|
|
138
|
+
)
|
|
139
|
+
from opentelemetry.sdk.resources import Resource
|
|
140
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
141
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
142
|
+
|
|
143
|
+
# Create resource with session ID
|
|
144
|
+
resource = Resource.create(
|
|
145
|
+
{
|
|
146
|
+
"service.name": service_name,
|
|
147
|
+
"session.id": session_id,
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Create tracer provider
|
|
152
|
+
_tracer_provider = TracerProvider(resource=resource)
|
|
153
|
+
|
|
154
|
+
# Add OTLP exporter pointing to Chronos
|
|
155
|
+
otlp_exporter = OTLPSpanExporter(endpoint=f"{otlp_endpoint.rstrip('/')}/v1/traces")
|
|
156
|
+
_tracer_provider.add_span_processor(BatchSpanProcessor(otlp_exporter))
|
|
157
|
+
|
|
158
|
+
# Set as global tracer provider
|
|
159
|
+
trace.set_tracer_provider(_tracer_provider)
|
|
160
|
+
|
|
161
|
+
_initialized = True
|
|
162
|
+
|
|
163
|
+
# Install logging handler to capture Python logs
|
|
164
|
+
if capture_logging:
|
|
165
|
+
_logging_handler = OTelLoggingHandler()
|
|
166
|
+
_logging_handler.setLevel(log_level)
|
|
167
|
+
# Add to root logger to capture all logs
|
|
168
|
+
logging.getLogger().addHandler(_logging_handler)
|
|
169
|
+
|
|
170
|
+
# Use print to ensure this shows regardless of logging config
|
|
171
|
+
print(f"[OTel] Tracing initialized: service={service_name}, session={session_id}, endpoint={otlp_endpoint}")
|
|
172
|
+
_module_logger.info(
|
|
173
|
+
f"OTel tracing initialized: service={service_name}, "
|
|
174
|
+
f"session={session_id}, endpoint={otlp_endpoint}, "
|
|
175
|
+
f"capture_logging={capture_logging}"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
except ImportError as e:
|
|
179
|
+
print(f"[OTel] OpenTelemetry SDK not installed: {e}")
|
|
180
|
+
_module_logger.warning(f"OpenTelemetry SDK not installed: {e}")
|
|
181
|
+
except Exception as e:
|
|
182
|
+
print(f"[OTel] Failed to initialize tracing: {e}")
|
|
183
|
+
_module_logger.error(f"Failed to initialize tracing: {e}")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def shutdown_tracing() -> None:
|
|
187
|
+
"""Shutdown the tracer provider, flush spans, and remove logging handler."""
|
|
188
|
+
global _tracer_provider, _logging_handler, _initialized
|
|
189
|
+
|
|
190
|
+
# Remove logging handler first
|
|
191
|
+
if _logging_handler:
|
|
192
|
+
try:
|
|
193
|
+
logging.getLogger().removeHandler(_logging_handler)
|
|
194
|
+
except Exception:
|
|
195
|
+
pass
|
|
196
|
+
_logging_handler = None
|
|
197
|
+
|
|
198
|
+
if _tracer_provider:
|
|
199
|
+
try:
|
|
200
|
+
_tracer_provider.shutdown()
|
|
201
|
+
_module_logger.info("OTel tracing shutdown complete")
|
|
202
|
+
except Exception as e:
|
|
203
|
+
_module_logger.warning(f"Error shutting down tracer: {e}")
|
|
204
|
+
|
|
205
|
+
_tracer_provider = None
|
|
206
|
+
_initialized = False
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def get_tracer(name: str = "plato") -> Tracer:
|
|
210
|
+
"""Get a tracer instance.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
name: Tracer name (default: "plato")
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
OpenTelemetry Tracer
|
|
217
|
+
"""
|
|
218
|
+
return trace.get_tracer(name)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def is_initialized() -> bool:
|
|
222
|
+
"""Check if OTel tracing is initialized."""
|
|
223
|
+
return _initialized
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def instrument(service_name: str = "plato-agent") -> Tracer:
|
|
227
|
+
"""Initialize OTel tracing from environment variables.
|
|
228
|
+
|
|
229
|
+
Reads the following env vars:
|
|
230
|
+
- OTEL_EXPORTER_OTLP_ENDPOINT: Chronos OTLP endpoint (required for tracing)
|
|
231
|
+
- SESSION_ID: Chronos session ID (default: "local")
|
|
232
|
+
|
|
233
|
+
If OTEL_EXPORTER_OTLP_ENDPOINT is not set, returns a no-op tracer.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
service_name: Name of the service for traces
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
OpenTelemetry Tracer
|
|
240
|
+
"""
|
|
241
|
+
import os
|
|
242
|
+
|
|
243
|
+
otel_endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT")
|
|
244
|
+
session_id = os.environ.get("SESSION_ID", "local")
|
|
245
|
+
|
|
246
|
+
if not otel_endpoint:
|
|
247
|
+
# Return default tracer (no-op if no provider configured)
|
|
248
|
+
return trace.get_tracer(service_name)
|
|
249
|
+
|
|
250
|
+
# Initialize tracing
|
|
251
|
+
init_tracing(
|
|
252
|
+
service_name=service_name,
|
|
253
|
+
session_id=session_id,
|
|
254
|
+
otlp_endpoint=otel_endpoint,
|
|
255
|
+
capture_logging=True,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return trace.get_tracer(service_name)
|