sunholo 0.129.0__py3-none-any.whl → 0.129.2__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.
- sunholo/custom_logging.py +195 -30
- {sunholo-0.129.0.dist-info → sunholo-0.129.2.dist-info}/METADATA +1 -1
- {sunholo-0.129.0.dist-info → sunholo-0.129.2.dist-info}/RECORD +7 -8
- sunholo/traced_logging.py +0 -70
- {sunholo-0.129.0.dist-info → sunholo-0.129.2.dist-info}/WHEEL +0 -0
- {sunholo-0.129.0.dist-info → sunholo-0.129.2.dist-info}/entry_points.txt +0 -0
- {sunholo-0.129.0.dist-info → sunholo-0.129.2.dist-info}/licenses/LICENSE.txt +0 -0
- {sunholo-0.129.0.dist-info → sunholo-0.129.2.dist-info}/top_level.txt +0 -0
sunholo/custom_logging.py
CHANGED
@@ -89,53 +89,109 @@ class GoogleCloudLogging:
|
|
89
89
|
}
|
90
90
|
return None
|
91
91
|
|
92
|
+
def update_trace_id(self, trace_id):
|
93
|
+
"""
|
94
|
+
Updates the trace ID to be included in all logs.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
trace_id (str): The trace ID to add to all logs.
|
98
|
+
"""
|
99
|
+
self.trace_id = trace_id
|
100
|
+
|
101
|
+
def _append_trace_id(self, log_text):
|
102
|
+
"""
|
103
|
+
Appends trace ID to log text if available.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
log_text (str): The log message.
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
str: Log message with trace ID prefixed if available.
|
110
|
+
"""
|
111
|
+
if hasattr(self, 'trace_id') and self.trace_id:
|
112
|
+
return f"{self.logger_name}-{self.trace_id} {log_text}"
|
113
|
+
return log_text
|
114
|
+
|
115
|
+
def _add_trace_to_struct(self, log_struct):
|
116
|
+
"""
|
117
|
+
Adds trace ID to structured log data if available.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
log_struct (dict): The structured log data.
|
121
|
+
|
122
|
+
Returns:
|
123
|
+
dict: Log structure with trace ID added if available.
|
124
|
+
"""
|
125
|
+
if not isinstance(log_struct, dict):
|
126
|
+
return log_struct
|
127
|
+
|
128
|
+
if hasattr(self, 'trace_id') and self.trace_id:
|
129
|
+
return {**log_struct, "trace_id": self.trace_id}
|
130
|
+
return log_struct
|
131
|
+
|
92
132
|
def structured_log(self, log_text=None, log_struct=None, logger_name=None, severity="INFO"):
|
93
133
|
"""
|
94
134
|
Writes log entries to the specified logger as either text or structured data.
|
95
|
-
|
135
|
+
|
96
136
|
Args:
|
97
137
|
log_text (str, optional): The log message as a text string. Defaults to None.
|
98
138
|
log_struct (dict, optional): The log message as a dictionary for structured log. Defaults to None.
|
99
|
-
logger_name (str, optional): The name of the logger to which to write the log entries.
|
100
|
-
|
139
|
+
logger_name (str, optional): The name of the logger to which to write the log entries.
|
140
|
+
e.g. logName="run.googleapis.com%2Fstderr"
|
101
141
|
severity (str, optional): The severity level of the log entry. Defaults to "INFO".
|
102
142
|
"""
|
103
|
-
|
104
143
|
from .utils.version import sunholo_version
|
105
|
-
|
106
|
-
log_text
|
107
|
-
|
144
|
+
|
145
|
+
# Add version to log_text if it exists
|
146
|
+
if log_text is not None:
|
147
|
+
log_text = f"[{sunholo_version()}] {log_text}"
|
148
|
+
log_text = self._append_trace_id(log_text)
|
149
|
+
|
150
|
+
# Always create or update log_struct with trace_id if available
|
151
|
+
if not log_struct:
|
152
|
+
log_struct = {}
|
153
|
+
|
154
|
+
# Make sure log_struct is a dictionary
|
155
|
+
if not isinstance(log_struct, dict):
|
156
|
+
log_struct = {"original_non_dict_value": str(log_struct)}
|
157
|
+
|
158
|
+
# Add trace ID to log_struct
|
159
|
+
if hasattr(self, 'trace_id') and self.trace_id:
|
160
|
+
log_struct["trace_id"] = self.trace_id
|
161
|
+
|
162
|
+
# Add version to log_struct
|
163
|
+
log_struct["version"] = sunholo_version()
|
164
|
+
|
108
165
|
if not logger_name and not self.logger_name:
|
109
166
|
raise ValueError("Must provide a logger name e.g. 'run.googleapis.com%2Fstderr'")
|
110
167
|
|
111
168
|
from .utils.gcp import is_running_on_gcp, is_gcp_logged_in
|
112
169
|
if not is_running_on_gcp() and not is_gcp_logged_in():
|
170
|
+
import logging as log
|
113
171
|
log.basicConfig(level=log.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
114
172
|
if log_text:
|
115
|
-
log.info(f"[{severity}][{logger_name or self.logger_name}][{self.version}] - {log_text}")
|
116
|
-
|
173
|
+
log.info(f"[{severity}][{logger_name or self.logger_name}][{self.version}] - {log_text} - {log_struct}")
|
174
|
+
else:
|
117
175
|
log.info(f"[{severity}][{logger_name or self.logger_name}][{self.version}] - {str(log_struct)}")
|
118
|
-
|
176
|
+
return
|
177
|
+
|
119
178
|
logger = self.client.logger(logger_name or self.logger_name)
|
120
|
-
|
121
179
|
caller_info = self._get_caller_info()
|
122
|
-
|
180
|
+
|
181
|
+
# Always log struct, and include message if provided
|
123
182
|
if log_text:
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
logger.log_text(log_text, severity=severity, source_location=caller_info)
|
128
|
-
else:
|
129
|
-
try:
|
130
|
-
turn_to_text = str(log_text)
|
131
|
-
logger.log_text(turn_to_text, severity=severity, source_location=caller_info)
|
132
|
-
except Exception as err:
|
133
|
-
print(f"Could not log this: {log_text=} - {str(err)}")
|
134
|
-
|
135
|
-
elif log_struct:
|
136
|
-
if not isinstance(log_struct, dict):
|
137
|
-
raise ValueError("log_struct must be a dictionary.")
|
183
|
+
log_struct["message"] = log_text
|
184
|
+
|
185
|
+
try:
|
138
186
|
logger.log_struct(log_struct, severity=severity, source_location=caller_info)
|
187
|
+
except Exception as err:
|
188
|
+
print(f"Failed to log struct: {err}")
|
189
|
+
# Fallback to text logging
|
190
|
+
fallback_message = log_text if log_text else str(log_struct)
|
191
|
+
try:
|
192
|
+
logger.log_text(fallback_message, severity=severity, source_location=caller_info)
|
193
|
+
except Exception as text_err:
|
194
|
+
print(f"Even fallback text logging failed: {text_err}")
|
139
195
|
|
140
196
|
def debug(self, log_text=None, log_struct=None):
|
141
197
|
|
@@ -217,6 +273,53 @@ def setup_logging(logger_name=None, log_level=logging.INFO, project_id=None):
|
|
217
273
|
# Now you can use Python's logging module as usual
|
218
274
|
import logging
|
219
275
|
log.info('This is an info message that will be sent to Google Cloud log.')
|
276
|
+
|
277
|
+
# Basic structured logging
|
278
|
+
log.info(log_struct={"action": "user_login", "user_id": "12345"})
|
279
|
+
|
280
|
+
# Structured logging with trace ID
|
281
|
+
log.update_trace_id("abc-123")
|
282
|
+
log.info(log_struct={"action": "process_started", "file_count": 42})
|
283
|
+
# This will include trace_id: "abc-123" in the logged structure
|
284
|
+
|
285
|
+
# Logging with both text and structure
|
286
|
+
log.info(
|
287
|
+
log_text="Processing completed successfully",
|
288
|
+
log_struct={"duration_ms": 1234, "items_processed": 100}
|
289
|
+
)
|
290
|
+
|
291
|
+
# Logging error with structured context
|
292
|
+
try:
|
293
|
+
# Some operation
|
294
|
+
process_data()
|
295
|
+
except Exception as e:
|
296
|
+
log.error(
|
297
|
+
log_text=f"Error processing data: {str(e)}",
|
298
|
+
log_struct={
|
299
|
+
"error_type": type(e).__name__,
|
300
|
+
"file_name": "example.csv",
|
301
|
+
"line_number": 42
|
302
|
+
}
|
303
|
+
)
|
304
|
+
|
305
|
+
# More complex structured logging
|
306
|
+
log.info(log_struct={
|
307
|
+
"request": {
|
308
|
+
"method": "POST",
|
309
|
+
"path": "/api/data",
|
310
|
+
"user_agent": "Mozilla/5.0...",
|
311
|
+
"ip": "192.168.1.1"
|
312
|
+
},
|
313
|
+
"response": {
|
314
|
+
"status_code": 200,
|
315
|
+
"processing_time_ms": 345,
|
316
|
+
"bytes_sent": 1024
|
317
|
+
},
|
318
|
+
"metadata": {
|
319
|
+
"version": "1.2.3",
|
320
|
+
"environment": "production"
|
321
|
+
}
|
322
|
+
})
|
220
323
|
|
221
324
|
Note:
|
222
325
|
This function requires that the 'google-cloud-logging' library is installed and
|
@@ -252,9 +355,22 @@ def setup_logging(logger_name=None, log_level=logging.INFO, project_id=None):
|
|
252
355
|
|
253
356
|
return log
|
254
357
|
|
255
|
-
|
256
|
-
|
257
|
-
|
358
|
+
# for debugging
|
359
|
+
def safe_log_struct(log, severity, message, struct):
|
360
|
+
try:
|
361
|
+
if severity == "INFO":
|
362
|
+
log.info(log_text=message, log_struct=struct)
|
363
|
+
elif severity == "ERROR":
|
364
|
+
log.error(log_text=message, log_struct=struct)
|
365
|
+
# Add other severity levels as needed
|
366
|
+
except Exception as e:
|
367
|
+
print(f"Logging error: {e}")
|
368
|
+
print(f"Failed to log structure: {struct}")
|
369
|
+
# Fallback to simple text logging
|
370
|
+
if severity == "INFO":
|
371
|
+
log.info(f"{message} (struct logging failed)")
|
372
|
+
elif severity == "ERROR":
|
373
|
+
log.error(f"{message} (struct logging failed)")
|
258
374
|
|
259
375
|
def log_folder_location(folder_name):
|
260
376
|
# Get the current working directory
|
@@ -277,4 +393,53 @@ def get_logger():
|
|
277
393
|
_logger = setup_logging("sunholo")
|
278
394
|
return _logger
|
279
395
|
|
280
|
-
log = get_logger()
|
396
|
+
log = get_logger()
|
397
|
+
|
398
|
+
"""
|
399
|
+
# Basic structured logging
|
400
|
+
log.info(log_struct={"action": "user_login", "user_id": "12345"})
|
401
|
+
|
402
|
+
# Structured logging with trace ID
|
403
|
+
log.update_trace_id("abc-123")
|
404
|
+
log.info(log_struct={"action": "process_started", "file_count": 42})
|
405
|
+
# This will include trace_id: "abc-123" in the logged structure
|
406
|
+
|
407
|
+
# Logging with both text and structure
|
408
|
+
log.info(
|
409
|
+
log_text="Processing completed successfully",
|
410
|
+
log_struct={"duration_ms": 1234, "items_processed": 100}
|
411
|
+
)
|
412
|
+
|
413
|
+
# Logging error with structured context
|
414
|
+
try:
|
415
|
+
# Some operation
|
416
|
+
process_data()
|
417
|
+
except Exception as e:
|
418
|
+
log.error(
|
419
|
+
log_text=f"Error processing data: {str(e)}",
|
420
|
+
log_struct={
|
421
|
+
"error_type": type(e).__name__,
|
422
|
+
"file_name": "example.csv",
|
423
|
+
"line_number": 42
|
424
|
+
}
|
425
|
+
)
|
426
|
+
|
427
|
+
# More complex structured logging
|
428
|
+
log.info(log_struct={
|
429
|
+
"request": {
|
430
|
+
"method": "POST",
|
431
|
+
"path": "/api/data",
|
432
|
+
"user_agent": "Mozilla/5.0...",
|
433
|
+
"ip": "192.168.1.1"
|
434
|
+
},
|
435
|
+
"response": {
|
436
|
+
"status_code": 200,
|
437
|
+
"processing_time_ms": 345,
|
438
|
+
"bytes_sent": 1024
|
439
|
+
},
|
440
|
+
"metadata": {
|
441
|
+
"version": "1.2.3",
|
442
|
+
"environment": "production"
|
443
|
+
}
|
444
|
+
})
|
445
|
+
"""
|
@@ -1,7 +1,6 @@
|
|
1
1
|
sunholo/__init__.py,sha256=InRbX4V0-qdNHo9zYH3GEye7ASLR6LX8-SMvPV4Jsaw,1212
|
2
|
-
sunholo/custom_logging.py,sha256
|
2
|
+
sunholo/custom_logging.py,sha256=-n7YvgqWQ8nRjCKl1bfcbeZOMTV3m-rBdZGuVyXWDR8,17433
|
3
3
|
sunholo/langchain_types.py,sha256=uZ4zvgej_f7pLqjtu4YP7qMC_eZD5ym_5x4pyvA1Ih4,1834
|
4
|
-
sunholo/traced_logging.py,sha256=TaAK9gFQ63h700dXfCK-nLcyMvh00N-oZ-xlz4VAZ_4,2891
|
5
4
|
sunholo/agents/__init__.py,sha256=X2I3pPkGeKWjc3d0QgSpkTyqD8J8JtrEWqwrumf1MMc,391
|
6
5
|
sunholo/agents/chat_history.py,sha256=Gph_CdlP2otYnNdR1q1Umyyyvcad2F6K3LxU5yBQ9l0,5387
|
7
6
|
sunholo/agents/dispatch_to_qa.py,sha256=NHihwAoCJ5_Lk11e_jZnucVUGQyZHCB-YpkfMHBCpQk,8882
|
@@ -169,9 +168,9 @@ sunholo/vertex/init.py,sha256=1OQwcPBKZYBTDPdyU7IM4X4OmiXLdsNV30C-fee2scQ,2875
|
|
169
168
|
sunholo/vertex/memory_tools.py,sha256=tBZxqVZ4InTmdBvLlOYwoSEWu4-kGquc-gxDwZCC4FA,7667
|
170
169
|
sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
|
171
170
|
sunholo/vertex/type_dict_to_json.py,sha256=uTzL4o9tJRao4u-gJOFcACgWGkBOtqACmb6ihvCErL8,4694
|
172
|
-
sunholo-0.129.
|
173
|
-
sunholo-0.129.
|
174
|
-
sunholo-0.129.
|
175
|
-
sunholo-0.129.
|
176
|
-
sunholo-0.129.
|
177
|
-
sunholo-0.129.
|
171
|
+
sunholo-0.129.2.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
|
172
|
+
sunholo-0.129.2.dist-info/METADATA,sha256=-G-cXG-Chu5B82pBh_4sww6YzAOkcF_6BIt1RDTJuMk,10084
|
173
|
+
sunholo-0.129.2.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
174
|
+
sunholo-0.129.2.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
|
175
|
+
sunholo-0.129.2.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
|
176
|
+
sunholo-0.129.2.dist-info/RECORD,,
|
sunholo/traced_logging.py
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
from .custom_logging import setup_logging, GoogleCloudLogging
|
2
|
-
|
3
|
-
class TracedGoogleCloudLogging(GoogleCloudLogging):
|
4
|
-
"""Extends GoogleCloudLogging with trace ID functionality."""
|
5
|
-
|
6
|
-
def __init__(self, project_id=None, log_level=None, logger_name=None, trace_id=None):
|
7
|
-
super().__init__(project_id, log_level, logger_name)
|
8
|
-
self.trace_id = trace_id
|
9
|
-
|
10
|
-
def update_trace_id(self, trace_id):
|
11
|
-
"""Update the trace ID to be included in all logs."""
|
12
|
-
self.trace_id = trace_id
|
13
|
-
|
14
|
-
def _get_trace_prefix(self):
|
15
|
-
"""Get the trace prefix if a trace ID is set."""
|
16
|
-
return f"aitana-{self.trace_id} " if self.trace_id else ""
|
17
|
-
|
18
|
-
def structured_log(self, log_text=None, log_struct=None, logger_name=None, severity="INFO"):
|
19
|
-
"""Override to add trace ID to logs."""
|
20
|
-
if log_text and self.trace_id:
|
21
|
-
log_text = f"{self._get_trace_prefix()}{log_text}"
|
22
|
-
|
23
|
-
if log_struct and self.trace_id:
|
24
|
-
if isinstance(log_struct, dict):
|
25
|
-
log_struct = {**log_struct, "trace_id": self.trace_id}
|
26
|
-
|
27
|
-
# Call the parent method with the modified parameters
|
28
|
-
super().structured_log(log_text, log_struct, logger_name, severity)
|
29
|
-
|
30
|
-
def setup_traced_logging(logger_name=None, log_level=None, project_id=None, trace_id=None):
|
31
|
-
"""Sets up traced logging that includes a trace ID in all logs."""
|
32
|
-
# First get the base logger from the original setup_logging
|
33
|
-
base_logger = setup_logging(logger_name, log_level, project_id)
|
34
|
-
|
35
|
-
# If it's a GoogleCloudLogging instance, wrap it with our traced version
|
36
|
-
if isinstance(base_logger, GoogleCloudLogging):
|
37
|
-
traced_logger = TracedGoogleCloudLogging(
|
38
|
-
project_id=base_logger.project_id,
|
39
|
-
log_level=base_logger.log_level,
|
40
|
-
logger_name=base_logger.logger_name,
|
41
|
-
trace_id=trace_id
|
42
|
-
)
|
43
|
-
traced_logger.client = base_logger.client # Reuse the client
|
44
|
-
return traced_logger
|
45
|
-
|
46
|
-
# For standard Python logging, we can add a filter
|
47
|
-
import logging
|
48
|
-
class TraceFilter(logging.Filter):
|
49
|
-
def __init__(self, trace_id=None):
|
50
|
-
super().__init__()
|
51
|
-
self.trace_id = trace_id
|
52
|
-
|
53
|
-
def update_trace_id(self, trace_id):
|
54
|
-
self.trace_id = trace_id
|
55
|
-
|
56
|
-
def filter(self, record):
|
57
|
-
if self.trace_id:
|
58
|
-
prefix = f"aitana-{self.trace_id} "
|
59
|
-
if not record.msg.startswith(prefix):
|
60
|
-
record.msg = f"{prefix}{record.msg}"
|
61
|
-
return True
|
62
|
-
|
63
|
-
# Add the trace filter to the logger
|
64
|
-
trace_filter = TraceFilter(trace_id)
|
65
|
-
base_logger.addFilter(trace_filter)
|
66
|
-
|
67
|
-
# Add the update method to the standard logger
|
68
|
-
base_logger.update_trace_id = trace_filter.update_trace_id
|
69
|
-
|
70
|
-
return base_logger
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|