ipulse-shared-core-ftredge 2.55__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.
- {ipulse_shared_core_ftredge-2.55/src/ipulse_shared_core_ftredge.egg-info → ipulse_shared_core_ftredge-2.56}/PKG-INFO +1 -1
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/setup.py +1 -1
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/__init__.py +1 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/enums_common_utils.py +42 -17
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/utils_common.py +177 -116
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/utils_gcp.py +6 -9
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56/src/ipulse_shared_core_ftredge.egg-info}/PKG-INFO +1 -1
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/LICENCE +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/README.md +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/pyproject.toml +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/setup.cfg +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/__init__.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/enums_data_eng.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/enums_module_fincore.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/enums/enums_modules.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/__init__.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/audit_log_firestore.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/organisation.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/pulse_enums.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/resource_catalog_item.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/user_auth.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/user_profile.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/user_profile_update.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/models/user_status.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/tests/__init__.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/tests/test.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge/utils_templates_and_schemas.py +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge.egg-info/SOURCES.txt +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge.egg-info/dependency_links.txt +0 -0
- {ipulse_shared_core_ftredge-2.55 → ipulse_shared_core_ftredge-2.56}/src/ipulse_shared_core_ftredge.egg-info/requires.txt +0 -0
- {ipulse_shared_core_ftredge-2.55 → 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.
|
|
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.
|
|
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=[
|
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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,13 @@ 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
|
-
|
|
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
25
|
context: str = None, description: str = None,
|
|
23
26
|
e: Exception = None, e_type: str = None, e_message: str = None, e_traceback: str = None,
|
|
24
|
-
log_status: LogStatus = LogStatus.OPEN, subject: str = None
|
|
27
|
+
log_status: LogStatus = LogStatus.OPEN, subject: str = None, systems_impacted: List[str] = None
|
|
25
28
|
):
|
|
26
29
|
if e is not None:
|
|
27
30
|
e_type = type(e).__name__ if e_type is None else e_type
|
|
@@ -35,19 +38,60 @@ class ContextLog:
|
|
|
35
38
|
self.description = description
|
|
36
39
|
self._base_context = base_context
|
|
37
40
|
self._context = context
|
|
41
|
+
self._systems_impacted = systems_impacted if systems_impacted else []
|
|
38
42
|
self.collector_id = collector_id
|
|
39
43
|
self.exception_type = e_type
|
|
40
44
|
self.exception_message = e_message
|
|
41
|
-
self.exception_traceback =
|
|
45
|
+
self.exception_traceback = e_traceback
|
|
42
46
|
self.log_status = log_status
|
|
43
47
|
self.timestamp = datetime.now(timezone.utc).isoformat()
|
|
44
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
|
+
|
|
45
85
|
def _format_traceback(self, e_traceback, e_message):
|
|
46
86
|
if not e_traceback or e_traceback == 'None\n':
|
|
47
87
|
return None
|
|
48
88
|
|
|
49
89
|
traceback_lines = e_traceback.splitlines()
|
|
50
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
|
+
|
|
51
95
|
# Remove lines that are part of the exception message if they are present in traceback
|
|
52
96
|
message_lines = e_message.splitlines() if e_message else []
|
|
53
97
|
if message_lines:
|
|
@@ -70,135 +114,94 @@ class ContextLog:
|
|
|
70
114
|
else:
|
|
71
115
|
combined_lines.append(line)
|
|
72
116
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
combined_lines[:keep_lines_start] +
|
|
81
|
-
['... (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) ...'] +
|
|
82
124
|
combined_lines[-keep_lines_end:]
|
|
83
125
|
)
|
|
84
|
-
else:
|
|
85
|
-
formatted_traceback = '\n'.join(combined_lines)
|
|
86
126
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@property
|
|
90
|
-
def base_context(self):
|
|
91
|
-
return self._base_context
|
|
92
|
-
|
|
93
|
-
@base_context.setter
|
|
94
|
-
def base_context(self, value):
|
|
95
|
-
self._base_context = value
|
|
96
|
-
|
|
97
|
-
@property
|
|
98
|
-
def context(self):
|
|
99
|
-
return self._context
|
|
100
|
-
|
|
101
|
-
@context.setter
|
|
102
|
-
def context(self, value):
|
|
103
|
-
self._context = value
|
|
127
|
+
formatted_traceback = '\n'.join(combined_lines)
|
|
104
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
|
+
)
|
|
138
|
+
return formatted_traceback
|
|
105
139
|
|
|
106
|
-
def to_dict(self, size_limit=256 * 1024 * 0.80):
|
|
140
|
+
def to_dict(self, max_field_len:int =10000, size_limit:float=256 * 1024 * 0.80):
|
|
107
141
|
size_limit = int(size_limit) # Ensure size_limit is an integer
|
|
108
142
|
|
|
109
|
-
#
|
|
110
|
-
|
|
111
|
-
|
|
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)),
|
|
112
147
|
("level_code", self.level.value),
|
|
113
|
-
("level_name", self.level.name),
|
|
114
|
-
("
|
|
115
|
-
("
|
|
116
|
-
("
|
|
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)))
|
|
117
159
|
]
|
|
118
160
|
|
|
119
|
-
# Additional fields to be truncated if necessary. Shorter fields are truncated first so that remaining size can increase for longer fields.
|
|
120
|
-
additional_fields = [
|
|
121
|
-
("subject", self.subject),
|
|
122
|
-
("description", self.description),
|
|
123
|
-
("exception_type", self.exception_type),
|
|
124
|
-
("exception_message", self.exception_message),
|
|
125
|
-
("context", self.context), # special sizing rules apply to it
|
|
126
|
-
("exception_traceback", self.exception_traceback)
|
|
127
|
-
]
|
|
128
|
-
|
|
129
|
-
all_fields = priority_fields + additional_fields
|
|
130
|
-
non_zero_fields = [(key, value) for key, value in all_fields if value is not None]
|
|
131
|
-
|
|
132
|
-
total_size = 0
|
|
133
|
-
truncated = False # Flag to indicate if truncation happened
|
|
134
|
-
|
|
135
161
|
# Function to calculate the byte size of a JSON-encoded field
|
|
136
162
|
def field_size(key, value):
|
|
137
163
|
return len(json.dumps({key: value}).encode('utf-8'))
|
|
138
164
|
|
|
165
|
+
# Function to truncate a value based on its type
|
|
139
166
|
# Function to truncate a value based on its type
|
|
140
167
|
def truncate_value(value, max_size):
|
|
141
168
|
if isinstance(value, str):
|
|
142
169
|
half_size = max_size // 2
|
|
143
170
|
return value[:half_size] + '...' + value[-(max_size - half_size - 3):]
|
|
144
|
-
|
|
145
|
-
half_size = max_size // 2
|
|
146
|
-
return list(value[:half_size]) + ['...'] + list(value[-(max_size - half_size - 1):])
|
|
147
|
-
elif isinstance(value, set):
|
|
148
|
-
truncated_set = set(list(value)[:max_size // 2]) | set(list(value)[-(max_size // 2):])
|
|
149
|
-
return truncated_set
|
|
150
|
-
elif isinstance(value, dict):
|
|
151
|
-
truncated_dict = {k: truncate_value(v, max_size // len(value)) for k, v in list(value.items())}
|
|
152
|
-
return truncated_dict
|
|
153
|
-
else:
|
|
154
|
-
return value
|
|
171
|
+
return value
|
|
155
172
|
|
|
156
|
-
|
|
157
|
-
for key, value in
|
|
158
|
-
|
|
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))
|
|
159
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)
|
|
160
180
|
log_dict = {}
|
|
161
|
-
|
|
181
|
+
truncated = False
|
|
182
|
+
|
|
162
183
|
if total_size > size_limit:
|
|
163
|
-
truncated = True
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
# Add priority fields first with possible truncation
|
|
171
|
-
for key, value in priority_fields:
|
|
172
|
-
if value is not None:
|
|
173
|
-
truncated_value = value
|
|
174
|
-
if isinstance(value, (str, list, tuple, set, dict)) and field_size(key, value) > max_size_per_field:
|
|
175
|
-
truncated_value = truncate_value(value, max_size_per_field)
|
|
176
|
-
log_dict[key] = truncated_value
|
|
177
|
-
total_size += field_size(key, truncated_value)
|
|
178
|
-
else:
|
|
179
|
-
log_dict[key] = value
|
|
180
|
-
|
|
181
|
-
# Calculate remaining size for additional fields
|
|
182
|
-
remaining_size = size_limit - total_size
|
|
183
|
-
|
|
184
|
-
# Handle remaining additional fields
|
|
185
|
-
non_zero_additional_fields = [field for field in additional_fields[1:] if field[1]]
|
|
186
|
-
remaining_field_size = remaining_size // len(non_zero_additional_fields) if non_zero_additional_fields else 0
|
|
187
|
-
|
|
188
|
-
for key, value in additional_fields[1:]:
|
|
189
|
-
if value is not None:
|
|
190
|
-
if field_size(key, value) > remaining_field_size:
|
|
191
|
-
truncated_value = truncate_value(value, remaining_field_size)
|
|
192
|
-
else:
|
|
193
|
-
truncated_value = value
|
|
194
|
-
log_dict[key] = truncated_value
|
|
195
|
-
remaining_size -= field_size(key, truncated_value)
|
|
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
|
|
196
191
|
else:
|
|
197
|
-
|
|
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
|
|
198
202
|
else:
|
|
199
|
-
log_dict = dict(
|
|
203
|
+
log_dict = dict(fields)
|
|
200
204
|
|
|
201
|
-
# Add trunc flag to the log dictionary
|
|
202
205
|
log_dict['trunc'] = truncated
|
|
203
206
|
|
|
204
207
|
return log_dict
|
|
@@ -208,8 +211,9 @@ class Pipelinemon:
|
|
|
208
211
|
WARNING_START_CODE = LogLevel.WARNING.value
|
|
209
212
|
NOTICE_START_CODE = LogLevel.NOTICE.value
|
|
210
213
|
SUCCESS_START_CODE = LogLevel.SUCCESS.value
|
|
214
|
+
INFO_START_CODE = LogLevel.INFO.value
|
|
211
215
|
|
|
212
|
-
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):
|
|
213
217
|
self._id = str(uuid.uuid4())
|
|
214
218
|
self._logs = []
|
|
215
219
|
self._early_stop = False
|
|
@@ -217,11 +221,15 @@ class Pipelinemon:
|
|
|
217
221
|
self._warnings_count = 0
|
|
218
222
|
self._notices_count = 0
|
|
219
223
|
self._successes_count = 0
|
|
224
|
+
self._infos_count = 0
|
|
225
|
+
self._systems_impacted = []
|
|
220
226
|
self._level_counts = {level.name: 0 for level in LogLevel}
|
|
221
227
|
self._base_context = base_context
|
|
222
228
|
self._context_stack = []
|
|
223
229
|
self._target_logs = target_logs.value
|
|
224
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
|
|
225
233
|
|
|
226
234
|
def _initialize_logger(self, logger_name):
|
|
227
235
|
if logger_name:
|
|
@@ -256,6 +264,38 @@ class Pipelinemon:
|
|
|
256
264
|
def id(self):
|
|
257
265
|
return self._id
|
|
258
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
|
+
|
|
259
299
|
@property
|
|
260
300
|
def early_stop(self):
|
|
261
301
|
return self._early_stop
|
|
@@ -265,24 +305,23 @@ class Pipelinemon:
|
|
|
265
305
|
if create_error_log:
|
|
266
306
|
if pop_context:
|
|
267
307
|
self.pop_context()
|
|
268
|
-
self.add_log(ContextLog(level=LogLevel.
|
|
308
|
+
self.add_log(ContextLog(level=LogLevel.ERROR_PIPELINE_THRESHOLD_REACHED,
|
|
269
309
|
subject="EARLY_STOP",
|
|
270
310
|
description=f"Total MAX_ERRORS_TOLERANCE of {max_errors_tolerance} has been reached."))
|
|
271
311
|
|
|
272
312
|
def reset_early_stop(self):
|
|
273
313
|
self._early_stop = False
|
|
274
314
|
|
|
275
|
-
def get_early_stop(self):
|
|
276
|
-
return self._early_stop
|
|
277
315
|
|
|
278
|
-
def add_log(self, log: ContextLog):
|
|
316
|
+
def add_log(self, log: ContextLog, ):
|
|
279
317
|
if (self._target_logs == TargetLogs.SUCCESSES and log.level >=self.NOTICE_START_CODE) or \
|
|
280
318
|
(self._target_logs == TargetLogs.WARNINGS_AND_ERRORS and log.level.value < self.WARNING_START_CODE):
|
|
281
319
|
raise ValueError(f"Invalid log level {log.level.name} for Pipelinemon target logs setup: {self._target_logs}")
|
|
282
320
|
log.base_context = self.base_context
|
|
283
321
|
log.context = self.current_context
|
|
284
322
|
log.collector_id = self.id
|
|
285
|
-
|
|
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)
|
|
286
325
|
self._logs.append(log_dict)
|
|
287
326
|
self._update_counts(log_dict)
|
|
288
327
|
|
|
@@ -306,6 +345,7 @@ class Pipelinemon:
|
|
|
306
345
|
self._warnings_count = 0
|
|
307
346
|
self._notices_count = 0
|
|
308
347
|
self._successes_count = 0
|
|
348
|
+
self._infos_count = 0
|
|
309
349
|
self._level_counts = {level.name: 0 for level in LogLevel}
|
|
310
350
|
|
|
311
351
|
def clear_logs(self):
|
|
@@ -344,6 +384,15 @@ class Pipelinemon:
|
|
|
344
384
|
def count_successes(self):
|
|
345
385
|
return self._successes_count
|
|
346
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
|
+
|
|
347
396
|
def count_all_logs(self):
|
|
348
397
|
return len(self._logs)
|
|
349
398
|
|
|
@@ -400,7 +449,13 @@ class Pipelinemon:
|
|
|
400
449
|
def count_successes_for_current_and_nested_contexts(self):
|
|
401
450
|
return self._count_logs(self.current_context, level_code_min=self.SUCCESS_START_CODE, level_code_max=self.NOTICE_START_CODE-1)
|
|
402
451
|
|
|
403
|
-
def
|
|
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):
|
|
404
459
|
def log_message(message):
|
|
405
460
|
if logger:
|
|
406
461
|
logger.info(message)
|
|
@@ -429,9 +484,11 @@ class Pipelinemon:
|
|
|
429
484
|
local_path=local_path,
|
|
430
485
|
logger=logger,
|
|
431
486
|
max_retries=max_retries,
|
|
432
|
-
overwrite_if_exists=
|
|
487
|
+
overwrite_if_exists=overwrite_if_exists,
|
|
488
|
+
increment_if_exists=increment_if_exists
|
|
489
|
+
|
|
433
490
|
)
|
|
434
|
-
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')}.")
|
|
435
492
|
except Exception as e:
|
|
436
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)}")
|
|
437
494
|
|
|
@@ -469,6 +526,8 @@ class Pipelinemon:
|
|
|
469
526
|
self._notices_count -= 1
|
|
470
527
|
elif self.SUCCESS_START_CODE <= level_code < self.NOTICE_START_CODE:
|
|
471
528
|
self._successes_count -= 1
|
|
529
|
+
elif self.INFO_START_CODE <= level_code < self.SUCCESS_START_CODE:
|
|
530
|
+
self._infos_count -= 1
|
|
472
531
|
self._level_counts[level_name] -= 1
|
|
473
532
|
else:
|
|
474
533
|
if level_code >= self.ERROR_START_CODE:
|
|
@@ -479,4 +538,6 @@ class Pipelinemon:
|
|
|
479
538
|
self._notices_count += 1
|
|
480
539
|
elif self.SUCCESS_START_CODE <= level_code < self.NOTICE_START_CODE:
|
|
481
540
|
self._successes_count += 1
|
|
482
|
-
self.
|
|
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
|
-
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|