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 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. e.g.
100
- logName="run.googleapis.com%2Fstderr"
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 = f"[{sunholo_version()}] {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
- elif log_struct:
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
- if isinstance(log_struct, dict):
125
- logger.log_struct(log_struct, severity=severity, source_location=caller_info)
126
- elif isinstance(log_struct, str):
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sunholo
3
- Version: 0.129.0
3
+ Version: 0.129.2
4
4
  Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
5
5
  Author-email: Holosun ApS <multivac@sunholo.com>
6
6
  License: Apache License, Version 2.0
@@ -1,7 +1,6 @@
1
1
  sunholo/__init__.py,sha256=InRbX4V0-qdNHo9zYH3GEye7ASLR6LX8-SMvPV4Jsaw,1212
2
- sunholo/custom_logging.py,sha256=YfIN1oP3dOEkkYkyRBU8BGS3uJFGwUDsFCl8mIVbwvE,12225
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.0.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
173
- sunholo-0.129.0.dist-info/METADATA,sha256=57vcuIcATCDaw7YigDz2bXfPqGp3PBSToaTLJLIiJRo,10084
174
- sunholo-0.129.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
175
- sunholo-0.129.0.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
176
- sunholo-0.129.0.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
177
- sunholo-0.129.0.dist-info/RECORD,,
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