ipulse-shared-core-ftredge 2.54__tar.gz → 2.56__tar.gz

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.

Potentially problematic release.


This version of ipulse-shared-core-ftredge might be problematic. Click here for more details.

Files changed (31) hide show
  1. {ipulse_shared_core_ftredge-2.54/src/ipulse_shared_core_ftredge.egg-info → ipulse_shared_core_ftredge-2.56}/PKG-INFO +1 -1
  2. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/setup.py +1 -1
  3. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/__init__.py +1 -1
  4. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/__init__.py +1 -0
  5. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/enums_common_utils.py +42 -17
  6. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/utils_common.py +206 -59
  7. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/utils_gcp.py +6 -9
  8. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56/src/ipulse_shared_core_ftredge.egg-info}/PKG-INFO +1 -1
  9. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/LICENCE +0 -0
  10. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/README.md +0 -0
  11. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/pyproject.toml +0 -0
  12. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/setup.cfg +0 -0
  13. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/enums_data_eng.py +0 -0
  14. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/enums_module_fincore.py +0 -0
  15. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/enums_modules.py +0 -0
  16. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/__init__.py +0 -0
  17. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/audit_log_firestore.py +0 -0
  18. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/organisation.py +0 -0
  19. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/pulse_enums.py +0 -0
  20. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/resource_catalog_item.py +0 -0
  21. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/user_auth.py +0 -0
  22. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/user_profile.py +0 -0
  23. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/user_profile_update.py +0 -0
  24. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/user_status.py +0 -0
  25. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/tests/__init__.py +0 -0
  26. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/tests/test.py +0 -0
  27. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/utils_templates_and_schemas.py +0 -0
  28. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge.egg-info/SOURCES.txt +0 -0
  29. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge.egg-info/dependency_links.txt +0 -0
  30. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge.egg-info/requires.txt +0 -0
  31. {ipulse_shared_core_ftredge-2.54 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ipulse_shared_core_ftredge
3
- Version: 2.54
3
+ Version: 2.56
4
4
  Summary: Shared Core models and Logger util for the Pulse platform project. Using AI for financial advisory and investment management.
5
5
  Home-page: https://github.com/TheFutureEdge/ipulse_shared_core
6
6
  Author: Russlan Ramdowar
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
3
3
 
4
4
  setup(
5
5
  name='ipulse_shared_core_ftredge',
6
- version='2.54',
6
+ version='2.56',
7
7
  package_dir={'': 'src'}, # Specify the source directory
8
8
  packages=find_packages(where='src'), # Look for packages in 'src'
9
9
  install_requires=[
@@ -5,7 +5,7 @@ from .utils_gcp import (setup_gcp_logger_and_error_report,
5
5
  write_csv_to_gcs, write_json_to_gcs)
6
6
  from .utils_templates_and_schemas import (create_bigquery_schema_from_json,
7
7
  check_format_against_schema_template)
8
- from .utils_common import (ContextLog, PipelineWatcher)
8
+ from .utils_common import (ContextLog, Pipelinemon)
9
9
 
10
10
  from .enums import (TargetLogs, LogLevel, Unit, Frequency,
11
11
  Module, SubModule, BaseDataCategory,
@@ -4,6 +4,7 @@
4
4
  # pylint: disable=missing-class-docstring
5
5
 
6
6
  from .enums_common_utils import (LogLevel,
7
+ SystemsImpacted,
7
8
  TargetLogs,
8
9
  Unit,
9
10
  Frequency)
@@ -2,10 +2,28 @@
2
2
  # pylint: disable=missing-module-docstring
3
3
  # pylint: disable=missing-function-docstring
4
4
  # pylint: disable=missing-class-docstring
5
+ # pylint: disable=line-too-long
5
6
 
6
7
  from enum import Enum
7
8
 
8
9
 
10
+ class SystemsImpacted(Enum):
11
+ NO = "__no"
12
+ YES = "__yes"
13
+ INVESTIGATE = "__investigate"
14
+ MULTIPLE = "__multiple"
15
+ DB = "db"
16
+ BQ_TABLE= "bq_table"
17
+ BQ_TABLES = "bq_tables"
18
+ GCS_BUCKET = "gcs_bucket"
19
+ GCS_BUCKETS = "gcs_buckets"
20
+ GCS_BUCKET_FILE = "gcs_bucket_file"
21
+ GCS_BUCKET_FILES = "gcs_bucket_files"
22
+ API = "api"
23
+ APIS = "apis"
24
+ LOCAL_FILE = "local_file"
25
+ LOCAL_FILES = "local_files"
26
+
9
27
  class TargetLogs(Enum):
10
28
  MIXED="mixed_logs"
11
29
  SUCCESSES = "success_logs"
@@ -15,22 +33,27 @@ class TargetLogs(Enum):
15
33
  WARNINGS_AND_ERRORS = "warn_n_err_logs"
16
34
  ERRORS = "error_logs"
17
35
 
18
-
19
36
  class LogLevel(Enum):
20
37
  """
21
38
  Standardized notice levels for data engineering pipelines,
22
39
  designed for easy analysis and identification of manual
23
40
  intervention needs.
24
41
  """
25
- DEBUG = 100 # Detailed debug information (for development/troubleshooting)
42
+ DEBUG = 10 # Detailed debug information (for development/troubleshooting)
43
+
44
+ INFO = 100
45
+ INFO_PERSISTNACE_COMPLETE= 101
46
+ INFO_UPDATE_COMPLETE = 102
47
+ INFO_DELETE_COMPLETE = 103
26
48
 
27
- INFO = 200
28
49
  SUCCESS = 201
50
+ SUCCESS_WITH_NOTICES = 211
51
+ SUCCESS_WITH_WARNINGS = 212
29
52
 
30
53
  NOTICE = 300 # Maybe same file or data already fully or partially exists
31
54
  NOTICE_ALREADY_EXISTS = 301 # Data already exists, no action required
32
55
  NOTICE_PARTIAL_EXISTS = 302 # Partial data exists, no action required
33
- NOTICE_CANCELLED = 303 # Data processing cancelled, no action required
56
+ NOTICE_ACTION_CANCELLED = 303 # Data processing cancelled, no action required
34
57
 
35
58
  # Warnings indicate potential issues that might require attention:
36
59
  WARNING = 400 # General warning, no immediate action required
@@ -40,18 +63,22 @@ class LogLevel(Enum):
40
63
  WARNING_FIX_REQUIRED = 404 # Action required, pipeline can likely continue
41
64
 
42
65
  ERROR = 500 # General error, no immediate action required
43
- # Errors indicate a problem that disrupts normal pipeline execution:
44
- ERROR_EXCEPTION_REDO = 501
45
- ERROR_CUSTOM_REDO = 502 # Temporary error, automatic retry likely to succeed
46
-
47
- ERROR_EXCEPTION_INVESTIGATE = 601 # Exception occured after some data was likely persisted (e.g., to GCS or BQ)
48
- ERROR_CUSTOM_INVESTIGATE= 602
49
- ERROR_EXCEPTION_PERSTISTANCE = 603 # Exception occured after data was persisted (e.g., to GCS or BQ)
50
- ERROR_CUSTOM_PERSTISTANCE = 604
51
66
 
67
+ ERROR_EXCEPTION = 501
68
+ ERROR_CUSTOM = 502 # Temporary error, automatic retry likely to succeed
69
+ ERROR_OPERATION_PARTIALLY_FAILED = 511 # Partial or full failure, manual intervention required
70
+ ERROR_OPERATION_FAILED = 512 # Operation failed, manual intervention required
71
+ ERORR_OPERATION_WITH_WARNINGS = 513 # Partial or full failure, manual intervention required
72
+ ERORR_OPERATION_WITH_ERRORS = 514 # Partial or full failure, manual intervention required
73
+ ERORR_OPERATION_WITH_WARNINGS_OR_ERRORS = 515 # Partial or full failure, manual intervention required
74
+
75
+ ERROR_THRESHOLD_REACHED = 551
76
+ ERROR_PIPELINE_THRESHOLD_REACHED = 552 # Error due to threshold reached, no immediate action required
77
+ ERROR_SUBTHRESHOLD_REACHED = 553 # Error due to threshold reached, no immediate action required
78
+ ERROR_DATA_QUALITY_THRESHOLD_REACHED = 554 # Error due to threshold reached, no immediate action required
52
79
  # Critical errors indicate severe failures requiring immediate attention:
53
- CRITICAL_SYSTEM_FAILURE = 701 # System-level failure (e.g., infrastructure), requires immediate action
54
- CRITICAL_PIPELINE_FAILURE = 702 # Complete pipeline failure, requires investigation and potential rollback
80
+ CRITICAL=600 # General critical error, requires immediate action
81
+ CRITICAL_SYSTEM_FAILURE = 601 # System-level failure (e.g., infrastructure, stackoverflow ), requires immediate action
55
82
 
56
83
  UNKNOWN=1001 # Unknown error, should not be used in normal operation
57
84
 
@@ -63,8 +90,6 @@ class LogStatus(Enum):
63
90
  RESOLVED = "resolved"
64
91
  IGNORED = "ignored"
65
92
  CANCELLED = "cancelled"
66
-
67
-
68
93
 
69
94
  ### Exception during full exection, partially saved
70
95
  # Exception during ensemble pipeline; modifications collected in local object , nothing persisted
@@ -143,4 +168,4 @@ class Frequency(Enum):
143
168
  THREE_M="3m"
144
169
  SIX_M="6m"
145
170
  ONE_Y="1y"
146
- THREE_Y="3y"
171
+ THREE_Y="3y"
@@ -3,6 +3,7 @@
3
3
  # pylint: disable=logging-fstring-interpolation
4
4
  # pylint: disable=line-too-long
5
5
  # pylint: disable=missing-class-docstring
6
+ # pylint: disable=broad-exception-caught
6
7
  import traceback
7
8
  import json
8
9
  import uuid
@@ -17,11 +18,14 @@ from ipulse_shared_core_ftredge.utils_gcp import write_json_to_gcs
17
18
  # ["data_import","data_quality", "data_processing","data_general","data_persistance","metadata_quality", "metadata_processing", "metadata_persistance","metadata_general"]
18
19
 
19
20
  class ContextLog:
20
- MAX_TRACEBACK_LINES = 14 # Define the maximum number of traceback lines to include
21
+ MAX_FIELD_LINES = 26 # Define the maximum number of traceback lines to include
22
+ MAX_FIELD_LENGTH=10000
23
+
21
24
  def __init__(self, level: LogLevel, base_context: str = None, collector_id: str = None,
22
- e: Exception = None, e_type: str = None, e_message: str = None, e_traceback: str = None,
23
- subject: str = None, description: str = None, context: str = None,
24
- log_status: LogStatus = LogStatus.OPEN):
25
+ context: str = None, description: str = None,
26
+ e: Exception = None, e_type: str = None, e_message: str = None, e_traceback: str = None,
27
+ log_status: LogStatus = LogStatus.OPEN, subject: str = None, systems_impacted: List[str] = None
28
+ ):
25
29
  if e is not None:
26
30
  e_type = type(e).__name__ if e_type is None else e_type
27
31
  e_message = str(e) if e_message is None else e_message
@@ -34,19 +38,60 @@ class ContextLog:
34
38
  self.description = description
35
39
  self._base_context = base_context
36
40
  self._context = context
41
+ self._systems_impacted = systems_impacted if systems_impacted else []
37
42
  self.collector_id = collector_id
38
43
  self.exception_type = e_type
39
44
  self.exception_message = e_message
40
- self.exception_traceback = self._format_traceback(e_traceback,e_message)
45
+ self.exception_traceback = e_traceback
41
46
  self.log_status = log_status
42
47
  self.timestamp = datetime.now(timezone.utc).isoformat()
43
48
 
49
+ @property
50
+ def base_context(self):
51
+ return self._base_context
52
+
53
+ @base_context.setter
54
+ def base_context(self, value):
55
+ self._base_context = value
56
+
57
+ @property
58
+ def context(self):
59
+ return self._context
60
+
61
+ @context.setter
62
+ def context(self, value):
63
+ self._context = value
64
+
65
+ @property
66
+ def systems_impacted(self):
67
+ return self._systems_impacted
68
+
69
+ @systems_impacted.setter
70
+ def systems_impacted(self, list_of_si: List[str]):
71
+ self._systems_impacted = list_of_si
72
+
73
+ def add_system_impacted(self, system_impacted: str):
74
+ if self._systems_impacted is None:
75
+ self._systems_impacted = []
76
+ self._systems_impacted.append(system_impacted)
77
+
78
+ def remove_system_impacted(self, system_impacted: str):
79
+ if self._systems_impacted is not None:
80
+ self._systems_impacted.remove(system_impacted)
81
+
82
+ def clear_systems_impacted(self):
83
+ self._systems_impacted = []
84
+
44
85
  def _format_traceback(self, e_traceback, e_message):
45
86
  if not e_traceback or e_traceback == 'None\n':
46
87
  return None
47
88
 
48
89
  traceback_lines = e_traceback.splitlines()
49
-
90
+
91
+ # Check if the traceback is within the limits
92
+ if len(traceback_lines) <= self.MAX_FIELD_LINES and len(e_traceback) <= self.MAX_FIELD_LENGTH:
93
+ return e_traceback
94
+
50
95
  # Remove lines that are part of the exception message if they are present in traceback
51
96
  message_lines = e_message.splitlines() if e_message else []
52
97
  if message_lines:
@@ -56,7 +101,7 @@ class ContextLog:
56
101
 
57
102
  # Filter out lines from third-party libraries (like site-packages)
58
103
  filtered_lines = [line for line in traceback_lines if "site-packages" not in line]
59
-
104
+
60
105
  # If filtering results in too few lines, revert to original traceback
61
106
  if len(filtered_lines) < 2:
62
107
  filtered_lines = traceback_lines
@@ -69,61 +114,106 @@ class ContextLog:
69
114
  else:
70
115
  combined_lines.append(line)
71
116
 
72
- # Determine the number of lines to keep from the start and end
73
- keep_lines_start = min(self.MAX_TRACEBACK_LINES // 2, len(combined_lines))
74
- keep_lines_end = min(self.MAX_TRACEBACK_LINES // 2, len(combined_lines) - keep_lines_start)
75
-
76
- if len(combined_lines) > self.MAX_TRACEBACK_LINES:
77
- # Include the first few and last few lines, and an indicator of truncation
78
- formatted_traceback = '\n'.join(
79
- combined_lines[:keep_lines_start] +
80
- ['... (truncated) ...'] +
117
+ # Ensure the number of lines doesn't exceed MAX_TRACEBACK_LINES
118
+ if len(combined_lines) > self.MAX_FIELD_LINES:
119
+ keep_lines_start = min(self.MAX_FIELD_LINES // 2, len(combined_lines))
120
+ keep_lines_end = min(self.MAX_FIELD_LINES // 2, len(combined_lines) - keep_lines_start)
121
+ combined_lines = (
122
+ combined_lines[:keep_lines_start] +
123
+ ['... (truncated) ...'] +
81
124
  combined_lines[-keep_lines_end:]
82
125
  )
83
- else:
84
- formatted_traceback = '\n'.join(combined_lines)
85
126
 
127
+ formatted_traceback = '\n'.join(combined_lines)
128
+
129
+ # Ensure the total length doesn't exceed MAX_TRACEBACK_LENGTH
130
+ if len(formatted_traceback) > self.MAX_FIELD_LENGTH:
131
+ truncated_length = self.MAX_FIELD_LENGTH - len('... (truncated) ...')
132
+ half_truncated_length = truncated_length // 2
133
+ formatted_traceback = (
134
+ formatted_traceback[:half_truncated_length] +
135
+ '\n... (truncated) ...\n' +
136
+ formatted_traceback[-half_truncated_length:]
137
+ )
86
138
  return formatted_traceback
87
139
 
88
- @property
89
- def base_context(self):
90
- return self._base_context
140
+ def to_dict(self, max_field_len:int =10000, size_limit:float=256 * 1024 * 0.80):
141
+ size_limit = int(size_limit) # Ensure size_limit is an integer
142
+
143
+ # Unified list of all fields
144
+ systems_impacted_str = f"{len(self.systems_impacted)} system(s): " + " ,,, ".join(self.systems_impacted) if self.systems_impacted else None
145
+ fields = [
146
+ ("log_status", str(self.log_status.name)),
147
+ ("level_code", self.level.value),
148
+ ("level_name", str(self.level.name)),
149
+ ("base_context", str(self.base_context)),
150
+ ("timestamp", str(self.timestamp)),
151
+ ("collector_id", str(self.collector_id)),
152
+ ("systems_impacted", systems_impacted_str),
153
+ ("context", str(self.context)), # special sizing rules apply to it
154
+ ("subject", str(self.subject)),
155
+ ("description", str(self.description)),
156
+ ("exception_type", str(self.exception_type)),
157
+ ("exception_message", str(self.exception_message)),
158
+ ("exception_traceback", str(self._format_traceback(self.exception_traceback,self.exception_message)))
159
+ ]
91
160
 
92
- @base_context.setter
93
- def base_context(self, value):
94
- self._base_context = value
161
+ # Function to calculate the byte size of a JSON-encoded field
162
+ def field_size(key, value):
163
+ return len(json.dumps({key: value}).encode('utf-8'))
164
+
165
+ # Function to truncate a value based on its type
166
+ # Function to truncate a value based on its type
167
+ def truncate_value(value, max_size):
168
+ if isinstance(value, str):
169
+ half_size = max_size // 2
170
+ return value[:half_size] + '...' + value[-(max_size - half_size - 3):]
171
+ return value
172
+
173
+ # Ensure no field exceeds max_field_len
174
+ for i, (key, value) in enumerate(fields):
175
+ if isinstance(value, str) and len(value) > max_field_len:
176
+ fields[i] = (key, truncate_value(value, max_field_len))
177
+
178
+ # Ensure total size of the dict doesn't exceed size_limit
179
+ total_size = sum(field_size(key, value) for key, value in fields)
180
+ log_dict = {}
181
+ truncated = False
182
+
183
+ if total_size > size_limit:
184
+ truncated = True
185
+ remaining_size = size_limit
186
+ remaining_fields = len(fields)
187
+
188
+ for key, value in fields:
189
+ if remaining_fields > 0:
190
+ max_size_per_field = remaining_size // remaining_fields
191
+ else:
192
+ max_size_per_field = 0
193
+
194
+ field_sz = field_size(key, value)
195
+ if field_sz > max_size_per_field:
196
+ value = truncate_value(value, max_size_per_field)
197
+ field_sz = field_size(key, value)
198
+
199
+ log_dict[key] = value
200
+ remaining_size -= field_sz
201
+ remaining_fields -= 1
202
+ else:
203
+ log_dict = dict(fields)
95
204
 
96
- @property
97
- def context(self):
98
- return self._context
205
+ log_dict['trunc'] = truncated
99
206
 
100
- @context.setter
101
- def context(self, value):
102
- self._context = value
207
+ return log_dict
103
208
 
104
- def to_dict(self):
105
- return {
106
- "base_context": self.base_context,
107
- "context": self.context,
108
- "level_code": self.level.value,
109
- "level_name": self.level.name,
110
- "subject": self.subject,
111
- "description": self.description,
112
- "exception_type": self.exception_type,
113
- "exception_message": self.exception_message,
114
- "exception_traceback": self.exception_traceback,
115
- "log_status": self.log_status.value,
116
- "collector_id": self.collector_id,
117
- "timestamp": self.timestamp
118
- }
119
-
120
- class PipelineWatcher:
209
+ class Pipelinemon:
121
210
  ERROR_START_CODE = LogLevel.ERROR.value
122
211
  WARNING_START_CODE = LogLevel.WARNING.value
123
212
  NOTICE_START_CODE = LogLevel.NOTICE.value
124
213
  SUCCESS_START_CODE = LogLevel.SUCCESS.value
214
+ INFO_START_CODE = LogLevel.INFO.value
125
215
 
126
- def __init__(self, base_context: str, target_logs: TargetLogs = TargetLogs.MIXED, logger_name=None):
216
+ def __init__(self, base_context: str, target_logs: TargetLogs = TargetLogs.MIXED, logger_name=None, max_log_field_size:int =10000, max_log_dict_size:float=256 * 1024 * 0.80):
127
217
  self._id = str(uuid.uuid4())
128
218
  self._logs = []
129
219
  self._early_stop = False
@@ -131,11 +221,15 @@ class PipelineWatcher:
131
221
  self._warnings_count = 0
132
222
  self._notices_count = 0
133
223
  self._successes_count = 0
224
+ self._infos_count = 0
225
+ self._systems_impacted = []
134
226
  self._level_counts = {level.name: 0 for level in LogLevel}
135
227
  self._base_context = base_context
136
228
  self._context_stack = []
137
229
  self._target_logs = target_logs.value
138
230
  self._logger = self._initialize_logger(logger_name)
231
+ self._max_log_field_size = max_log_field_size
232
+ self._max_log_dict_size = max_log_dict_size
139
233
 
140
234
  def _initialize_logger(self, logger_name):
141
235
  if logger_name:
@@ -170,6 +264,38 @@ class PipelineWatcher:
170
264
  def id(self):
171
265
  return self._id
172
266
 
267
+ @property
268
+ def systems_impacted(self):
269
+ return self._systems_impacted
270
+
271
+ @systems_impacted.setter
272
+ def systems_impacted(self, list_of_si: List[str]):
273
+ self._systems_impacted = list_of_si
274
+
275
+ def add_system_impacted(self, system_impacted: str):
276
+ if self._systems_impacted is None:
277
+ self._systems_impacted = []
278
+ self._systems_impacted.append(system_impacted)
279
+
280
+ def clear_systems_impacted(self):
281
+ self._systems_impacted = []
282
+
283
+ @property
284
+ def max_log_field_size(self):
285
+ return self._max_log_field_size
286
+
287
+ @max_log_field_size.setter
288
+ def max_log_field_size(self, value):
289
+ self._max_log_field_size = value
290
+
291
+ @property
292
+ def max_log_dict_size(self):
293
+ return self._max_log_dict_size
294
+
295
+ @max_log_dict_size.setter
296
+ def max_log_dict_size(self, value):
297
+ self._max_log_dict_size = value
298
+
173
299
  @property
174
300
  def early_stop(self):
175
301
  return self._early_stop
@@ -179,29 +305,28 @@ class PipelineWatcher:
179
305
  if create_error_log:
180
306
  if pop_context:
181
307
  self.pop_context()
182
- self.add_log(ContextLog(level=LogLevel.ERROR,
308
+ self.add_log(ContextLog(level=LogLevel.ERROR_PIPELINE_THRESHOLD_REACHED,
183
309
  subject="EARLY_STOP",
184
310
  description=f"Total MAX_ERRORS_TOLERANCE of {max_errors_tolerance} has been reached."))
185
311
 
186
312
  def reset_early_stop(self):
187
313
  self._early_stop = False
188
314
 
189
- def get_early_stop(self):
190
- return self._early_stop
191
315
 
192
- def add_log(self, log: ContextLog):
316
+ def add_log(self, log: ContextLog, ):
193
317
  if (self._target_logs == TargetLogs.SUCCESSES and log.level >=self.NOTICE_START_CODE) or \
194
318
  (self._target_logs == TargetLogs.WARNINGS_AND_ERRORS and log.level.value < self.WARNING_START_CODE):
195
- raise ValueError(f"Invalid log level {log.level.name} for Pipeline Watcher target logs setup: {self._target_logs}")
319
+ raise ValueError(f"Invalid log level {log.level.name} for Pipelinemon target logs setup: {self._target_logs}")
196
320
  log.base_context = self.base_context
197
321
  log.context = self.current_context
198
322
  log.collector_id = self.id
199
- log_dict = log.to_dict()
323
+ log.systems_impacted = self.systems_impacted
324
+ log_dict = log.to_dict(max_field_len=self.max_log_field_size, size_limit=self.max_log_dict_size)
200
325
  self._logs.append(log_dict)
201
326
  self._update_counts(log_dict)
202
327
 
203
328
  if self._logger:
204
- # We specifically want to avoid having an ERROR log level for this structured Pipeline Watcher reporting, to ensure Errors are alerting on Critical Application Services.
329
+ # We specifically want to avoid having an ERROR log level for this structured Pipelinemon reporting, to ensure Errors are alerting on Critical Application Services.
205
330
  # A single ERROR log level can be used for the entire pipeline, which shall be used at the end of the pipeline
206
331
  if log.level.value >= self.WARNING_START_CODE:
207
332
  self._logger.log_struct(log_dict, severity="WARNING")
@@ -220,6 +345,7 @@ class PipelineWatcher:
220
345
  self._warnings_count = 0
221
346
  self._notices_count = 0
222
347
  self._successes_count = 0
348
+ self._infos_count = 0
223
349
  self._level_counts = {level.name: 0 for level in LogLevel}
224
350
 
225
351
  def clear_logs(self):
@@ -258,6 +384,15 @@ class PipelineWatcher:
258
384
  def count_successes(self):
259
385
  return self._successes_count
260
386
 
387
+ def count_successes_with_notice(self):
388
+ return self.count_logs_by_level(LogLevel.SUCCESS_WITH_NOTICES)
389
+
390
+ def count_successes_no_notice(self):
391
+ return self.count_logs_by_level(LogLevel.SUCCESS)
392
+
393
+ def count_infos(self):
394
+ return self._infos_count
395
+
261
396
  def count_all_logs(self):
262
397
  return len(self._logs)
263
398
 
@@ -314,7 +449,13 @@ class PipelineWatcher:
314
449
  def count_successes_for_current_and_nested_contexts(self):
315
450
  return self._count_logs(self.current_context, level_code_min=self.SUCCESS_START_CODE, level_code_max=self.NOTICE_START_CODE-1)
316
451
 
317
- def export_logs_to_gcs_file(self, bucket_name, storage_client, file_prefix=None, file_name=None, top_level_context=None, save_locally=False, local_path=None, logger=None, max_retries=2):
452
+ def count_infos_for_current_context(self):
453
+ return self._count_logs(self.current_context, exact_match=True, level_code_min=self.INFO_START_CODE, level_code_max=self.SUCCESS_START_CODE-1)
454
+
455
+ def count_infos_for_current_and_nested_contexts(self):
456
+ return self._count_logs(self.current_context, level_code_min=self.INFO_START_CODE, level_code_max=self.SUCCESS_START_CODE-1)
457
+
458
+ def export_logs_to_gcs_file(self, bucket_name, storage_client, file_prefix=None, file_name=None, top_level_context=None, save_locally=False, overwrite_if_exists=False, increment_if_exists=True, local_path=None, logger=None, max_retries=2):
318
459
  def log_message(message):
319
460
  if logger:
320
461
  logger.info(message)
@@ -343,9 +484,11 @@ class PipelineWatcher:
343
484
  local_path=local_path,
344
485
  logger=logger,
345
486
  max_retries=max_retries,
346
- overwrite_if_exists=False
487
+ overwrite_if_exists=overwrite_if_exists,
488
+ increment_if_exists=increment_if_exists
489
+
347
490
  )
348
- log_message(f"{file_prefix} successfully saved (overwritten={result.get('gcs_file_overwritten')}) to GCS at {result.get('gcs_path')} and locally at {result.get('local_path')}.")
491
+ log_message(f"{file_prefix} successfully saved (overwritten={result.get('gcs_file_overwritten')}, incremented={result.get('gcs_file_saved_with_increment')}) to GCS at {result.get('gcs_path')} and locally at {result.get('local_path')}.")
349
492
  except Exception as e:
350
493
  log_error(f"Failed at export_logs_to_gcs_file for {file_prefix} for file {file_name} to bucket {bucket_name}: {type(e).__name__} - {str(e)}")
351
494
 
@@ -383,6 +526,8 @@ class PipelineWatcher:
383
526
  self._notices_count -= 1
384
527
  elif self.SUCCESS_START_CODE <= level_code < self.NOTICE_START_CODE:
385
528
  self._successes_count -= 1
529
+ elif self.INFO_START_CODE <= level_code < self.SUCCESS_START_CODE:
530
+ self._infos_count -= 1
386
531
  self._level_counts[level_name] -= 1
387
532
  else:
388
533
  if level_code >= self.ERROR_START_CODE:
@@ -393,4 +538,6 @@ class PipelineWatcher:
393
538
  self._notices_count += 1
394
539
  elif self.SUCCESS_START_CODE <= level_code < self.NOTICE_START_CODE:
395
540
  self._successes_count += 1
396
- self._level_counts[level_name] += 1
541
+ elif self.INFO_START_CODE <= level_code < self.SUCCESS_START_CODE:
542
+ self._infos_count += 1
543
+ self._level_counts[level_name] += 1
@@ -1,6 +1,9 @@
1
1
  # pylint: disable=missing-module-docstring
2
2
  # pylint: disable=missing-function-docstring
3
3
  # pylint: disable=missing-class-docstring
4
+ # pylint: disable=broad-exception-caught
5
+ # pylint: disable=line-too-long
6
+ # pylint: disable=unused-variable
4
7
  import json
5
8
  import csv
6
9
  from io import StringIO
@@ -23,7 +26,6 @@ from google.api_core.exceptions import NotFound
23
26
 
24
27
 
25
28
  ##### THIS APPROACH IS USED NOW ########
26
- ## TODO Fix the issue with POST 0B Nan.... printed in Cloud Logging , which is referring to posting to Cloud Logging probably.
27
29
  ENV = os.getenv('ENV', 'LOCAL').strip("'")
28
30
 
29
31
  def setup_gcp_logger_and_error_report(logger_name,level=logging.INFO, use_cloud_logging=True):
@@ -130,11 +132,7 @@ def write_json_to_gcs(bucket_name, storage_client, data, file_name,
130
132
  This function attempts to upload data to GCS. If the upload fails after
131
133
  retries and `save_locally` is True or `local_path` is provided, it attempts
132
134
  to save the data locally.
133
-
134
- Returns:
135
- dict: A dictionary containing the GCS path (or None if upload failed),
136
- the local path (or None if not saved locally), a boolean indicating if the file was overwritten,
137
- a boolean indicating if the file already existed, and a boolean indicating if the file was saved with an incremented name.
135
+ It also tries to handle file name conflicts by overwriting or incrementing. If both are provided as Ture, an exception will be raised.
138
136
  """
139
137
 
140
138
  def log_message(message):
@@ -214,14 +212,13 @@ def write_json_to_gcs(bucket_name, storage_client, data, file_name,
214
212
  local_path_final = os.path.join("/tmp", file_name)
215
213
  else:
216
214
  local_path_final = os.path.join(local_path, file_name)
217
-
215
+
218
216
  if os.path.exists(local_path_final):
219
217
  if increment_if_exists:
220
218
  increment = 0
221
219
  while os.path.exists(local_path_final):
222
220
  increment += 1
223
221
  local_path_final = os.path.join(local_path, f"{base_file_name}_{increment}{ext}")
224
- gcs_file_saved_with_increment = True
225
222
  elif not overwrite_if_exists:
226
223
  log_message(f"File {file_name} already exists locally at {local_path_final} and overwrite is set to False. Skipping save.")
227
224
  success = True
@@ -267,4 +264,4 @@ def write_csv_to_gcs(bucket_name, file_name, data, storage_client, logger,log_in
267
264
  except ValueError as e:
268
265
  logger.error(f"ValueError: {e}")
269
266
  except Exception as e:
270
- logger.error(f"An unexpected error occurred while writing CSV to GCS: {e}", exc_info=True)
267
+ logger.error(f"An unexpected error occurred while writing CSV to GCS: {e}", exc_info=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ipulse_shared_core_ftredge
3
- Version: 2.54
3
+ Version: 2.56
4
4
  Summary: Shared Core models and Logger util for the Pulse platform project. Using AI for financial advisory and investment management.
5
5
  Home-page: https://github.com/TheFutureEdge/ipulse_shared_core
6
6
  Author: Russlan Ramdowar