blaxel 0.1.14rc54__py3-none-any.whl → 0.1.14rc56__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.
- blaxel/common/logger.py +54 -2
- blaxel/instrumentation/manager.py +10 -8
- blaxel/jobs/__init__.py +151 -13
- {blaxel-0.1.14rc54.dist-info → blaxel-0.1.14rc56.dist-info}/METADATA +1 -1
- {blaxel-0.1.14rc54.dist-info → blaxel-0.1.14rc56.dist-info}/RECORD +7 -7
- {blaxel-0.1.14rc54.dist-info → blaxel-0.1.14rc56.dist-info}/WHEEL +0 -0
- {blaxel-0.1.14rc54.dist-info → blaxel-0.1.14rc56.dist-info}/licenses/LICENSE +0 -0
blaxel/common/logger.py
CHANGED
@@ -2,8 +2,55 @@
|
|
2
2
|
This module provides a custom colored formatter for logging and an initialization function
|
3
3
|
to set up logging configurations for Blaxel applications.
|
4
4
|
"""
|
5
|
-
|
5
|
+
import json
|
6
6
|
import logging
|
7
|
+
import os
|
8
|
+
from opentelemetry import trace
|
9
|
+
|
10
|
+
class JsonFormatter(logging.Formatter):
|
11
|
+
"""
|
12
|
+
A logger compatible with standard json logging.
|
13
|
+
"""
|
14
|
+
def __init__(self):
|
15
|
+
super().__init__()
|
16
|
+
self.trace_id_name = os.environ.get('BL_LOGGER_TRACE_ID', 'trace_id')
|
17
|
+
self.span_id_name = os.environ.get('BL_LOGGER_SPAN_ID', 'span_id')
|
18
|
+
self.labels_name = os.environ.get('BL_LOGGER_LABELS', 'labels')
|
19
|
+
self.trace_id_prefix = os.environ.get('BL_LOGGER_TRACE_ID_PREFIX', '')
|
20
|
+
self.span_id_prefix = os.environ.get('BL_LOGGER_SPAN_ID_PREFIX', '')
|
21
|
+
self.task_index = os.environ.get('BL_TASK_KEY', 'TASK_INDEX')
|
22
|
+
self.task_prefix = os.environ.get('BL_TASK_PREFIX', '')
|
23
|
+
self.execution_key = os.environ.get('BL_EXECUTION_KEY', 'BL_EXECUTION_ID')
|
24
|
+
self.execution_prefix = os.environ.get('BL_EXECUTION_PREFIX', '')
|
25
|
+
|
26
|
+
def format(self, record):
|
27
|
+
"""
|
28
|
+
Formats the log record by converting it to a JSON object with trace context and environment variables.
|
29
|
+
"""
|
30
|
+
log_entry = {
|
31
|
+
'message': record.getMessage(),
|
32
|
+
'severity': record.levelname,
|
33
|
+
self.labels_name: {}
|
34
|
+
}
|
35
|
+
|
36
|
+
# Add trace context if available
|
37
|
+
current_span = trace.get_current_span()
|
38
|
+
if current_span.is_recording():
|
39
|
+
span_context = current_span.get_span_context()
|
40
|
+
log_entry[self.trace_id_name] = f"{self.trace_id_prefix}{span_context.trace_id}"
|
41
|
+
log_entry[self.span_id_name] = f"{self.span_id_prefix}{span_context.span_id}"
|
42
|
+
|
43
|
+
# Add task ID if available
|
44
|
+
task_id = os.environ.get(self.task_index)
|
45
|
+
if task_id:
|
46
|
+
log_entry[self.labels_name]['blaxel-task'] = f"{self.task_prefix}{task_id}"
|
47
|
+
|
48
|
+
# Add execution ID if available
|
49
|
+
execution_id = os.environ.get(self.execution_key)
|
50
|
+
if execution_id:
|
51
|
+
log_entry[self.labels_name]['blaxel-execution'] = f"{self.execution_prefix}{execution_id.split('-')[-1]}"
|
52
|
+
|
53
|
+
return json.dumps(log_entry)
|
7
54
|
|
8
55
|
|
9
56
|
class ColoredFormatter(logging.Formatter):
|
@@ -51,5 +98,10 @@ def init_logger(log_level: str):
|
|
51
98
|
logging.getLogger('urllib3').setLevel(logging.CRITICAL)
|
52
99
|
logging.getLogger("httpx").setLevel(logging.CRITICAL)
|
53
100
|
handler = logging.StreamHandler()
|
54
|
-
|
101
|
+
|
102
|
+
logger_type = os.environ.get("BL_LOGGER", "http")
|
103
|
+
if logger_type == "json":
|
104
|
+
handler.setFormatter(JsonFormatter())
|
105
|
+
else:
|
106
|
+
handler.setFormatter(ColoredFormatter("%(levelname)s %(name)s - %(message)s"))
|
55
107
|
logging.basicConfig(level=log_level, handlers=[handler])
|
@@ -7,6 +7,7 @@ import importlib
|
|
7
7
|
import logging
|
8
8
|
import signal
|
9
9
|
import time
|
10
|
+
import os
|
10
11
|
from typing import Any, Dict, List, Optional, Type
|
11
12
|
|
12
13
|
from opentelemetry import metrics, trace
|
@@ -164,14 +165,15 @@ class TelemetryManager:
|
|
164
165
|
metrics.set_meter_provider(meter_provider)
|
165
166
|
self.meter = meter_provider.get_meter(__name__)
|
166
167
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
168
|
+
logger_type = os.environ.get("BL_LOGGER", "http")
|
169
|
+
if logger_type == "http":
|
170
|
+
self.logger_provider = LoggerProvider(resource=resource)
|
171
|
+
set_logger_provider(self.logger_provider)
|
172
|
+
self.logger_provider.add_log_record_processor(
|
173
|
+
AsyncLogRecordProcessor(self.get_log_exporter())
|
174
|
+
)
|
175
|
+
handler = LoggingHandler(level=logging.NOTSET, logger_provider=self.logger_provider)
|
176
|
+
logging.getLogger().addHandler(handler)
|
175
177
|
|
176
178
|
# Load and enable instrumentations
|
177
179
|
for name, mapping in MAPPINGS.items():
|
blaxel/jobs/__init__.py
CHANGED
@@ -2,10 +2,21 @@ import argparse
|
|
2
2
|
import os
|
3
3
|
import sys
|
4
4
|
import asyncio
|
5
|
-
|
5
|
+
import json
|
6
6
|
import requests
|
7
7
|
|
8
|
-
|
8
|
+
|
9
|
+
from typing import Any, Dict, Callable, Awaitable
|
10
|
+
from logging import getLogger
|
11
|
+
from ..client import client
|
12
|
+
from ..common.env import env
|
13
|
+
from ..common.internal import get_global_unique_hash
|
14
|
+
from ..common.settings import settings
|
15
|
+
from ..instrumentation.span import SpanManager
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
class BlJobWrapper:
|
9
20
|
def get_arguments(self) -> Dict[str, Any]:
|
10
21
|
if not os.getenv('BL_EXECUTION_DATA_URL'):
|
11
22
|
parser = argparse.ArgumentParser()
|
@@ -40,16 +51,143 @@ class BlJob:
|
|
40
51
|
Handles both async and sync functions.
|
41
52
|
Arguments are passed as keyword arguments to the function.
|
42
53
|
"""
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
54
|
+
attributes = {
|
55
|
+
"span.type": "job.start",
|
56
|
+
}
|
57
|
+
with SpanManager("blaxel-tracer").create_span("job.start", attributes) as span:
|
58
|
+
try:
|
59
|
+
parsed_args = self.get_arguments()
|
60
|
+
if asyncio.iscoroutinefunction(func):
|
61
|
+
asyncio.run(func(**parsed_args))
|
62
|
+
else:
|
63
|
+
func(**parsed_args)
|
64
|
+
span.set_attribute("job.start.success", True)
|
65
|
+
sys.exit(0)
|
66
|
+
except Exception as error:
|
67
|
+
span.set_attribute("job.start.error", str(error))
|
68
|
+
print('Job execution failed:', error, file=sys.stderr)
|
69
|
+
sys.exit(1)
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
logger = getLogger(__name__)
|
75
|
+
|
76
|
+
class BlJob:
|
77
|
+
def __init__(self, name: str):
|
78
|
+
self.name = name
|
79
|
+
|
80
|
+
@property
|
81
|
+
def internal_url(self):
|
82
|
+
"""Get the internal URL for the job using a hash of workspace and job name."""
|
83
|
+
hash = get_global_unique_hash(settings.workspace, "job", self.name)
|
84
|
+
return f"{settings.run_internal_protocol}://bl-{settings.env}-{hash}.{settings.run_internal_hostname}"
|
85
|
+
|
86
|
+
@property
|
87
|
+
def forced_url(self):
|
88
|
+
"""Get the forced URL from environment variables if set."""
|
89
|
+
env_var = self.name.replace("-", "_").upper()
|
90
|
+
if env[f"BL_JOB_{env_var}_URL"]:
|
91
|
+
return env[f"BL_JOB_{env_var}_URL"]
|
92
|
+
return None
|
93
|
+
|
94
|
+
@property
|
95
|
+
def external_url(self):
|
96
|
+
return f"{settings.run_url}/{settings.workspace}/jobs/{self.name}"
|
97
|
+
|
98
|
+
@property
|
99
|
+
def fallback_url(self):
|
100
|
+
if self.external_url != self.url:
|
101
|
+
return self.external_url
|
102
|
+
return None
|
103
|
+
|
104
|
+
@property
|
105
|
+
def url(self):
|
106
|
+
if self.forced_url:
|
107
|
+
return self.forced_url
|
108
|
+
if settings.run_internal_hostname:
|
109
|
+
return self.internal_url
|
110
|
+
return self.external_url
|
111
|
+
|
112
|
+
def call(self, url, input_data, headers: dict = {}, params: dict = {}):
|
113
|
+
body = {
|
114
|
+
"tasks": input_data
|
115
|
+
}
|
116
|
+
|
117
|
+
return client.get_httpx_client().post(
|
118
|
+
url+"/executions",
|
119
|
+
headers={
|
120
|
+
'Content-Type': 'application/json',
|
121
|
+
**headers
|
122
|
+
},
|
123
|
+
json=body,
|
124
|
+
params=params
|
125
|
+
)
|
126
|
+
|
127
|
+
async def acall(self, url, input_data, headers: dict = {}, params: dict = {}):
|
128
|
+
logger.debug(f"Job Calling: {self.name}")
|
129
|
+
body = {
|
130
|
+
"tasks": input_data
|
131
|
+
}
|
132
|
+
|
133
|
+
return await client.get_async_httpx_client().post(
|
134
|
+
url+"/executions",
|
135
|
+
headers={
|
136
|
+
'Content-Type': 'application/json',
|
137
|
+
**headers
|
138
|
+
},
|
139
|
+
json=body,
|
140
|
+
params=params
|
141
|
+
)
|
142
|
+
|
143
|
+
def run(self, input: Any, headers: dict = {}, params: dict = {}) -> str:
|
144
|
+
attributes = {
|
145
|
+
"job.name": self.name,
|
146
|
+
"span.type": "job.run",
|
147
|
+
}
|
148
|
+
with SpanManager("blaxel-tracer").create_active_span(self.name, attributes) as span:
|
149
|
+
logger.debug(f"Job Calling: {self.name}")
|
150
|
+
response = self.call(self.url, input, headers, params)
|
151
|
+
if response.status_code >= 400:
|
152
|
+
if not self.fallback_url:
|
153
|
+
span.set_attribute("job.run.error", response.text)
|
154
|
+
raise Exception(f"Job {self.name} returned status code {response.status_code} with body {response.text}")
|
155
|
+
response = self.call(self.fallback_url, input, headers, params)
|
156
|
+
if response.status_code >= 400:
|
157
|
+
span.set_attribute("job.run.error", response.text)
|
158
|
+
raise Exception(f"Job {self.name} returned status code {response.status_code} with body {response.text}")
|
159
|
+
span.set_attribute("job.run.result", response.text)
|
160
|
+
return response.text
|
161
|
+
|
162
|
+
async def arun(self, input: Any, headers: dict = {}, params: dict = {}) -> Awaitable[str]:
|
163
|
+
attributes = {
|
164
|
+
"job.name": self.name,
|
165
|
+
"span.type": "job.run",
|
166
|
+
}
|
167
|
+
with SpanManager("blaxel-tracer").create_active_span(self.name, attributes) as span:
|
168
|
+
logger.debug(f"Job Calling: {self.name}")
|
169
|
+
response = await self.acall(self.url, input, headers, params)
|
170
|
+
if response.status_code >= 400:
|
171
|
+
if not self.fallback_url:
|
172
|
+
span.set_attribute("job.run.error", response.text)
|
173
|
+
raise Exception(f"Job {self.name} returned status code {response.status_code} with body {response.text}")
|
174
|
+
response = await self.acall(self.fallback_url, input, headers, params)
|
175
|
+
if response.status_code >= 400:
|
176
|
+
span.set_attribute("job.run.error", response.text)
|
177
|
+
raise Exception(f"Job {self.name} returned status code {response.status_code} with body {response.text}")
|
178
|
+
span.set_attribute("job.run.result", response.text)
|
179
|
+
return response.text
|
180
|
+
|
181
|
+
def __str__(self):
|
182
|
+
return f"Job {self.name}"
|
183
|
+
|
184
|
+
def __repr__(self):
|
185
|
+
return self.__str__()
|
186
|
+
|
187
|
+
|
188
|
+
|
189
|
+
def bl_job(name: str):
|
190
|
+
return BlJob(name)
|
53
191
|
|
54
192
|
# Create a singleton instance
|
55
|
-
|
193
|
+
bl_start_job = BlJobWrapper()
|
@@ -263,14 +263,14 @@ blaxel/client/models/workspace_user.py,sha256=70CcifQWYbeWG7TDui4pblTzUe5sVK0AS1
|
|
263
263
|
blaxel/common/autoload.py,sha256=NFuK71-IHOY2JQyEBSjDCVfUaQ8D8PJsEUEryIdG4AU,263
|
264
264
|
blaxel/common/env.py,sha256=wTbzPDdNgz4HMJiS2NCZmQlN0qpxy1PQEYBaZgtvhoc,1247
|
265
265
|
blaxel/common/internal.py,sha256=PExgeKfJEmjINKreNb3r2nB5GAfG7uJhbfqHxuxBED8,2395
|
266
|
-
blaxel/common/logger.py,sha256=
|
266
|
+
blaxel/common/logger.py,sha256=7oWvrZ4fg7qUfrXe7oFAeH-1pTxadrvWQmPDn7bvbmQ,4111
|
267
267
|
blaxel/common/settings.py,sha256=7KTryuBdud0IfHqykX7xEEtpgq5M5h1Z8YEzYKsHB-Q,2327
|
268
268
|
blaxel/instrumentation/exporters.py,sha256=EoX3uaBVku1Rg49pSNXKFyHhgY5OV3Ih6UlqgjF5epw,1670
|
269
269
|
blaxel/instrumentation/log.py,sha256=4tGyvLg6r4DbjqJfajYbbZ1toUzF4Q4H7kHVqYWFAEA,2537
|
270
|
-
blaxel/instrumentation/manager.py,sha256=
|
270
|
+
blaxel/instrumentation/manager.py,sha256=vX8RT84upjzgCUeiULp9QpDSSNVnPNFxLq0sMVz4Pjs,8974
|
271
271
|
blaxel/instrumentation/map.py,sha256=zZoiUiQHmik5WQZ4VCWNARSa6ppMi0r7D6hlb41N-Mg,1589
|
272
272
|
blaxel/instrumentation/span.py,sha256=X2lwfu_dyxwQTMQJT2vbXOrbVSChEhjRLc413QOxQJM,3244
|
273
|
-
blaxel/jobs/__init__.py,sha256=
|
273
|
+
blaxel/jobs/__init__.py,sha256=S5z0p8qRhtNMUGo0lVTkePF0MTG3OEtVn5H5hIbyiho,6837
|
274
274
|
blaxel/mcp/__init__.py,sha256=KednMrtuc4Y0O3lv7u1Lla54FCk8UX9c1k0USjL3Ahk,69
|
275
275
|
blaxel/mcp/client.py,sha256=cFFXfpKXoMu8qTUly2ejF0pX2iBQkSNAxqwvDV1V6xY,4979
|
276
276
|
blaxel/mcp/server.py,sha256=GIldtA_NgIc2dzd7ZpPvpbhpIt_7AfKu5yS_YJ0bDGg,7310
|
@@ -341,7 +341,7 @@ blaxel/tools/llamaindex.py,sha256=-gQ-C9V_h9a11J4ItsbWjXrCJOg0lRKsb98v9rVsNak,71
|
|
341
341
|
blaxel/tools/openai.py,sha256=GuFXkj6bXEwldyVr89jEsRAi5ihZUVEVe327QuWiGNs,653
|
342
342
|
blaxel/tools/pydantic.py,sha256=CvnNbAG_J4yBtA-XFI4lQrq3FYKjNd39hu841vZT004,1801
|
343
343
|
blaxel/tools/types.py,sha256=YPCGJ4vZDhqR0X2H_TWtc5chQScsC32nGTQdRKJlO8Y,707
|
344
|
-
blaxel-0.1.
|
345
|
-
blaxel-0.1.
|
346
|
-
blaxel-0.1.
|
347
|
-
blaxel-0.1.
|
344
|
+
blaxel-0.1.14rc56.dist-info/METADATA,sha256=YeEOxc2aVPSXpo-6nXYXklVdvmr8zOE1XXaZGBuNYSE,11776
|
345
|
+
blaxel-0.1.14rc56.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
346
|
+
blaxel-0.1.14rc56.dist-info/licenses/LICENSE,sha256=p5PNQvpvyDT_0aYBDgmV1fFI_vAD2aSV0wWG7VTgRis,1069
|
347
|
+
blaxel-0.1.14rc56.dist-info/RECORD,,
|
File without changes
|
File without changes
|