ipulse-shared-core-ftredge 6.1.2__tar.gz → 6.3.1__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-6.1.2/src/ipulse_shared_core_ftredge.egg-info → ipulse_shared_core_ftredge-6.3.1}/PKG-INFO +2 -2
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/setup.py +2 -2
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/dependencies/authorization_api.py +1 -1
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/models/user_profile.py +4 -3
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/models/user_status.py +9 -5
- ipulse_shared_core_ftredge-6.3.1/src/ipulse_shared_core_ftredge/services/__init__.py +6 -0
- ipulse_shared_core_ftredge-6.1.2/src/ipulse_shared_core_ftredge/services/exceptions.py → ipulse_shared_core_ftredge-6.3.1/src/ipulse_shared_core_ftredge/services/base_exceptions.py +3 -1
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/services/base_firestore_service.py +3 -2
- ipulse_shared_core_ftredge-6.3.1/src/ipulse_shared_core_ftredge/services/fastapiservicemon.py +138 -0
- ipulse_shared_core_ftredge-6.3.1/src/ipulse_shared_core_ftredge/services/servicemon.py +240 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1/src/ipulse_shared_core_ftredge.egg-info}/PKG-INFO +2 -2
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge.egg-info/SOURCES.txt +3 -1
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge.egg-info/requires.txt +1 -1
- ipulse_shared_core_ftredge-6.1.2/src/ipulse_shared_core_ftredge/services/__init__.py +0 -4
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/LICENCE +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/README.md +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/pyproject.toml +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/setup.cfg +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/__init__.py +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/dependencies/__init__.py +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/dependencies/auth_router.py +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/dependencies/database.py +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/dependencies/token_validation.py +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/models/__init__.py +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/models/base_api_response.py +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/models/base_data_model.py +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/models/organization_profile.py +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/models/resource_catalog_item.py +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/models/subscription.py +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/models/user_auth.py +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge/models/user_profile_update.py +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge.egg-info/dependency_links.txt +0 -0
- {ipulse_shared_core_ftredge-6.1.2 → ipulse_shared_core_ftredge-6.3.1}/src/ipulse_shared_core_ftredge.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: ipulse_shared_core_ftredge
|
|
3
|
-
Version: 6.1
|
|
3
|
+
Version: 6.3.1
|
|
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
|
|
@@ -8,7 +8,7 @@ License-File: LICENCE
|
|
|
8
8
|
Requires-Dist: pydantic[email]~=2.5
|
|
9
9
|
Requires-Dist: python-dateutil~=2.8
|
|
10
10
|
Requires-Dist: pytest~=7.1
|
|
11
|
-
Requires-Dist: ipulse_shared_base_ftredge>=5.
|
|
11
|
+
Requires-Dist: ipulse_shared_base_ftredge>=5.2.1
|
|
12
12
|
Dynamic: author
|
|
13
13
|
Dynamic: home-page
|
|
14
14
|
Dynamic: requires-dist
|
|
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
|
|
|
3
3
|
|
|
4
4
|
setup(
|
|
5
5
|
name='ipulse_shared_core_ftredge',
|
|
6
|
-
version='6.1
|
|
6
|
+
version='6.3.1',
|
|
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=[
|
|
@@ -11,7 +11,7 @@ setup(
|
|
|
11
11
|
'pydantic[email]~=2.5',
|
|
12
12
|
'python-dateutil~=2.8',
|
|
13
13
|
'pytest~=7.1',
|
|
14
|
-
'ipulse_shared_base_ftredge>=5.
|
|
14
|
+
'ipulse_shared_base_ftredge>=5.2.1',
|
|
15
15
|
],
|
|
16
16
|
author='Russlan Ramdowar',
|
|
17
17
|
description='Shared Core models and Logger util for the Pulse platform project. Using AI for financial advisory and investment management.',
|
|
@@ -5,7 +5,7 @@ from datetime import datetime, timedelta, timezone
|
|
|
5
5
|
import httpx
|
|
6
6
|
from fastapi import HTTPException, Request
|
|
7
7
|
from google.cloud import firestore
|
|
8
|
-
from ipulse_shared_core_ftredge.services
|
|
8
|
+
from ipulse_shared_core_ftredge.services import ServiceError, AuthorizationError, ResourceNotFoundError
|
|
9
9
|
|
|
10
10
|
# Constants
|
|
11
11
|
USERS_STATUS_COLLECTION_NAME = "user-statuses"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
""" User Profile model representing user information. """
|
|
2
|
+
from datetime import date
|
|
3
3
|
from typing import Set, Optional, ClassVar
|
|
4
|
-
from pydantic import
|
|
4
|
+
from pydantic import EmailStr, Field, ConfigDict, field_validator
|
|
5
5
|
from ipulse_shared_base_ftredge import Layer, Module, list_as_lower_strings, Subject
|
|
6
6
|
from .base_data_model import BaseDataModel
|
|
7
7
|
|
|
@@ -83,6 +83,7 @@ class UserProfile(BaseDataModel):
|
|
|
83
83
|
@field_validator('id', mode='before')
|
|
84
84
|
@classmethod
|
|
85
85
|
def validate_or_generate_id(cls, v: Optional[str], info) -> str:
|
|
86
|
+
"""Validate or generate user ID based on user_uid."""
|
|
86
87
|
# If id is already provided (Firebase Auth case), return it
|
|
87
88
|
if v:
|
|
88
89
|
return v
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
""" User Status model for tracking user subscription and access rights. """
|
|
1
2
|
from datetime import datetime
|
|
2
3
|
from typing import Set, Optional, Dict, List, ClassVar
|
|
3
|
-
from pydantic import
|
|
4
|
-
from .subscription import Subscription
|
|
4
|
+
from pydantic import Field, ConfigDict, field_validator
|
|
5
5
|
from ipulse_shared_base_ftredge import Layer, Module, list_as_lower_strings, Subject
|
|
6
|
-
import
|
|
6
|
+
from .subscription import Subscription
|
|
7
7
|
from .base_data_model import BaseDataModel
|
|
8
|
+
|
|
8
9
|
# ORIGINAL AUTHOR ="Russlan Ramdowar;russlan@ftredge.com"
|
|
9
10
|
# CLASS_ORGIN_DATE=datetime(2024, 2, 12, 20, 5)
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
############################ !!!!! ALWAYS UPDATE SCHEMA VERSION , IF SCHEMA IS BEING MODIFIED !!! #################################
|
|
12
13
|
class UserStatus(BaseDataModel):
|
|
13
14
|
"""
|
|
14
15
|
User Status model for tracking user subscription and access rights.
|
|
@@ -35,7 +36,7 @@ class UserStatus(BaseDataModel):
|
|
|
35
36
|
frozen=True,
|
|
36
37
|
description="Version of this Class == version of DB Schema"
|
|
37
38
|
)
|
|
38
|
-
|
|
39
|
+
|
|
39
40
|
id : str = Field(
|
|
40
41
|
...,
|
|
41
42
|
description="User ID, format: {OBJ_REF}_{user_uid}"
|
|
@@ -90,6 +91,9 @@ class UserStatus(BaseDataModel):
|
|
|
90
91
|
@field_validator('id', mode='before')
|
|
91
92
|
@classmethod
|
|
92
93
|
def validate_or_generate_id(cls, v: Optional[str], info) -> str:
|
|
94
|
+
"""
|
|
95
|
+
Validate or generate the id field based on user_uid.
|
|
96
|
+
"""
|
|
93
97
|
# If id is already provided (Firebase Auth case), return it
|
|
94
98
|
if v:
|
|
95
99
|
return v
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from .base_firestore_service import BaseFirestoreService
|
|
2
|
+
|
|
3
|
+
from .base_exceptions import (BaseServiceException, ResourceNotFoundError, AuthorizationError,
|
|
4
|
+
ValidationError ,ServiceError)
|
|
5
|
+
from .servicemon import Servicemon
|
|
6
|
+
from .fastapiservicemon import FastAPIServiceMon
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
"""Base classes for service exceptions with enhanced logging"""
|
|
2
2
|
from typing import Optional, Any, Dict
|
|
3
3
|
import traceback
|
|
4
4
|
import logging
|
|
5
|
+
from fastapi import HTTPException
|
|
5
6
|
|
|
6
7
|
class BaseServiceException(HTTPException):
|
|
8
|
+
"""Base class for service exceptions with enhanced logging"""
|
|
7
9
|
def __init__(
|
|
8
10
|
self,
|
|
9
11
|
status_code: int,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
""" Base class for Firestore services with common CRUD operations """
|
|
2
|
+
from typing import Dict, Any, List, TypeVar, Generic
|
|
2
3
|
import logging
|
|
3
4
|
from datetime import datetime, timezone
|
|
4
5
|
from pydantic import BaseModel
|
|
5
6
|
from google.cloud import firestore
|
|
6
|
-
from .
|
|
7
|
+
from .base_exceptions import ResourceNotFoundError, ValidationError, ServiceError
|
|
7
8
|
|
|
8
9
|
T = TypeVar('T', bound=BaseModel)
|
|
9
10
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
""" FastAPI ServiceMon"""
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
from fastapi import Request
|
|
5
|
+
from ipulse_shared_base_ftredge import DataResource, Action, ProgressStatus, LogLevel
|
|
6
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
7
|
+
from . import Servicemon
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FastAPIServiceMon(Servicemon):
|
|
11
|
+
"""
|
|
12
|
+
Extension of Servicemon designed specifically for FastAPI applications.
|
|
13
|
+
Adds integration with FastAPI request/response lifecycle.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def get_fastapi_middleware():
|
|
18
|
+
"""
|
|
19
|
+
Creates a FastAPI middleware class that uses ServiceMon for request logging.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
A middleware class that can be registered with FastAPI
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ServiceMonMiddleware(BaseHTTPMiddleware):
|
|
27
|
+
"""
|
|
28
|
+
Middleware class for integrating ServiceMon into FastAPI request/response lifecycle.
|
|
29
|
+
"""
|
|
30
|
+
async def dispatch(self, request: Request, call_next):
|
|
31
|
+
# Create ServiceMon instance
|
|
32
|
+
logger_name = f"{request.app.state.env_prefix}__dp_core_api_live__apilogger"
|
|
33
|
+
logger = logging.getLogger(logger_name)
|
|
34
|
+
|
|
35
|
+
path = request.url.path
|
|
36
|
+
method = request.method
|
|
37
|
+
|
|
38
|
+
# Skip monitoring for certain paths
|
|
39
|
+
skip_paths = ["/health", "/metrics", "/docs", "/redoc", "/openapi.json"]
|
|
40
|
+
if any(path.startswith(skip_p) for skip_p in skip_paths):
|
|
41
|
+
return await call_next(request)
|
|
42
|
+
|
|
43
|
+
# Initialize ServiceMon
|
|
44
|
+
svcmon = Servicemon(
|
|
45
|
+
logger=logger,
|
|
46
|
+
base_context=f"API: {path}\nMethod: {method}",
|
|
47
|
+
service_name=f"API_{method}_{path.replace('/', '_')}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Start monitoring
|
|
51
|
+
client_ip = request.client.host if request.client else "unknown"
|
|
52
|
+
user_agent = request.headers.get("user-agent", "unknown")
|
|
53
|
+
svcmon.start(f"API Request {method} {path}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Add request info
|
|
57
|
+
svcmon.log(
|
|
58
|
+
level=LogLevel.INFO,
|
|
59
|
+
description=f"Request received for {method} {path}. Client IP: {client_ip}. User Agent: {user_agent}",
|
|
60
|
+
resource=DataResource.API_INTERNAL,
|
|
61
|
+
action=Action.EXECUTE,
|
|
62
|
+
progress_status=ProgressStatus.STARTED,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Process the request and catch any errors
|
|
66
|
+
try:
|
|
67
|
+
# Store ServiceMon in request state for handlers to access
|
|
68
|
+
request.state.svcmon = svcmon
|
|
69
|
+
|
|
70
|
+
# Process request
|
|
71
|
+
start_time = time.time()
|
|
72
|
+
response = await call_next(request)
|
|
73
|
+
process_time = int((time.time() - start_time) * 1000)
|
|
74
|
+
|
|
75
|
+
# Log response
|
|
76
|
+
status_code = response.status_code
|
|
77
|
+
progress_status = (
|
|
78
|
+
ProgressStatus.DONE
|
|
79
|
+
if 200 <= status_code < 300
|
|
80
|
+
else ProgressStatus.FINISHED_WITH_ISSUES
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
log_level = (
|
|
84
|
+
LogLevel.ERROR if status_code >= 500
|
|
85
|
+
else LogLevel.WARNING if status_code >= 400
|
|
86
|
+
else LogLevel.INFO
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
svcmon.log(
|
|
90
|
+
level=log_level,
|
|
91
|
+
description=f"Response sent: {status_code} in {process_time}ms for {method} {path}",
|
|
92
|
+
resource=DataResource.API_INTERNAL,
|
|
93
|
+
action=Action.EXECUTE,
|
|
94
|
+
progress_status=progress_status
|
|
95
|
+
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Finalize monitoring
|
|
99
|
+
svcmon.end(status=progress_status)
|
|
100
|
+
return response
|
|
101
|
+
|
|
102
|
+
except Exception as exc:
|
|
103
|
+
svcmon.error(
|
|
104
|
+
f"Error processing request: {str(exc)}",
|
|
105
|
+
resource=DataResource.API_INTERNAL,
|
|
106
|
+
action=Action.EXECUTE,
|
|
107
|
+
progress_status=ProgressStatus.FAILED,
|
|
108
|
+
e=exc
|
|
109
|
+
)
|
|
110
|
+
svcmon.end(status=ProgressStatus.FAILED)
|
|
111
|
+
raise
|
|
112
|
+
|
|
113
|
+
return ServiceMonMiddleware
|
|
114
|
+
|
|
115
|
+
# @staticmethod
|
|
116
|
+
# def setup_fastapi(app):
|
|
117
|
+
# """
|
|
118
|
+
# Configure a FastAPI application with ServiceMon integration.
|
|
119
|
+
|
|
120
|
+
# Args:
|
|
121
|
+
# app: The FastAPI application instance
|
|
122
|
+
# """
|
|
123
|
+
# from fastapi import FastAPI
|
|
124
|
+
|
|
125
|
+
# if not isinstance(app, FastAPI):
|
|
126
|
+
# raise TypeError("Expected FastAPI application instance")
|
|
127
|
+
|
|
128
|
+
# # Register middleware
|
|
129
|
+
# app.add_middleware(FastAPIServiceMon.get_fastapi_middleware())
|
|
130
|
+
|
|
131
|
+
# # Add dependency for route handlers
|
|
132
|
+
# from fastapi import Depends, Request
|
|
133
|
+
|
|
134
|
+
# async def get_servicemon(request: Request):
|
|
135
|
+
# """Dependency for accessing the current ServiceMon instance."""
|
|
136
|
+
# return getattr(request.state, "svcmon", None)
|
|
137
|
+
|
|
138
|
+
# app.dependency_overrides[FastAPIServiceMon] = get_servicemon
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ServiceMon - Lightweight monitoring for service functions and API endpoints
|
|
3
|
+
"""
|
|
4
|
+
import uuid
|
|
5
|
+
import time
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Dict, Any, Optional
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from ipulse_shared_base_ftredge import (LogLevel, AbstractResource,
|
|
10
|
+
ProgressStatus, Action, Resource,
|
|
11
|
+
Alert, StructLog)
|
|
12
|
+
|
|
13
|
+
class Servicemon:
|
|
14
|
+
"""
|
|
15
|
+
ServiceMon is a lightweight version of Pipelinemon designed specifically for monitoring
|
|
16
|
+
service functions like Cloud Functions and API endpoints.
|
|
17
|
+
|
|
18
|
+
It provides:
|
|
19
|
+
1. Structured logging with context tracking
|
|
20
|
+
2. Performance metrics capture
|
|
21
|
+
3. Service health monitoring
|
|
22
|
+
4. Integration with FastAPI request/response cycle
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, logger,
|
|
26
|
+
base_context: str,
|
|
27
|
+
service_name: str):
|
|
28
|
+
"""
|
|
29
|
+
Initialize ServiceMon with basic configuration.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
logger: The logger instance to use for logging
|
|
33
|
+
base_context: Base context information for all logs
|
|
34
|
+
service_name: Name of the service being monitored
|
|
35
|
+
"""
|
|
36
|
+
# Set up execution tracking details
|
|
37
|
+
self._start_time = None
|
|
38
|
+
self._service_name = service_name
|
|
39
|
+
|
|
40
|
+
timestamp = datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')
|
|
41
|
+
uuid_suffix = str(uuid.uuid4())[:8] # Take first 8 chars of UUID
|
|
42
|
+
self._id = f"{timestamp}_{uuid_suffix}"
|
|
43
|
+
|
|
44
|
+
# Set up context handling
|
|
45
|
+
self._base_context = base_context
|
|
46
|
+
self._context_stack = []
|
|
47
|
+
|
|
48
|
+
# Configure logging
|
|
49
|
+
self._logger = logger
|
|
50
|
+
|
|
51
|
+
# Metrics tracking
|
|
52
|
+
self._metrics = {
|
|
53
|
+
"status": ProgressStatus.NOT_STARTED.name,
|
|
54
|
+
"errors": 0,
|
|
55
|
+
"warnings": 0,
|
|
56
|
+
"start_time": None,
|
|
57
|
+
"end_time": None,
|
|
58
|
+
"duration_ms": None,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def id(self) -> str:
|
|
63
|
+
"""Get the unique ID for this service execution."""
|
|
64
|
+
return self._id
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def base_context(self) -> str:
|
|
68
|
+
"""Get the base context for this service execution."""
|
|
69
|
+
return self._base_context
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def service_name(self) -> str:
|
|
73
|
+
"""Get the service name being monitored."""
|
|
74
|
+
return self._service_name
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def metrics(self) -> Dict[str, Any]:
|
|
78
|
+
"""Get the current service metrics."""
|
|
79
|
+
return self._metrics.copy()
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def current_context(self) -> str:
|
|
83
|
+
"""Get the current context stack as a string."""
|
|
84
|
+
return " >> ".join(self._context_stack) if self._context_stack else "root"
|
|
85
|
+
|
|
86
|
+
@contextmanager
|
|
87
|
+
def context(self, context_name: str):
|
|
88
|
+
"""
|
|
89
|
+
Context manager for tracking execution context.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
context_name: The name of the current execution context
|
|
93
|
+
"""
|
|
94
|
+
self.push_context(context_name)
|
|
95
|
+
try:
|
|
96
|
+
yield
|
|
97
|
+
finally:
|
|
98
|
+
self.pop_context()
|
|
99
|
+
|
|
100
|
+
def push_context(self, context: str):
|
|
101
|
+
"""Add a context level to the stack."""
|
|
102
|
+
self._context_stack.append(context)
|
|
103
|
+
|
|
104
|
+
def pop_context(self):
|
|
105
|
+
"""Remove the most recent context from the stack."""
|
|
106
|
+
if self._context_stack:
|
|
107
|
+
return self._context_stack.pop()
|
|
108
|
+
|
|
109
|
+
def start(self, description: Optional[str] = None) -> None:
|
|
110
|
+
"""
|
|
111
|
+
Start monitoring a service execution.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
description: Optional description of what's being executed
|
|
115
|
+
"""
|
|
116
|
+
self._start_time = time.time()
|
|
117
|
+
self._metrics["start_time"] = datetime.now(timezone.utc).isoformat()
|
|
118
|
+
self._metrics["status"] = ProgressStatus.IN_PROGRESS.name
|
|
119
|
+
|
|
120
|
+
# Log the start event
|
|
121
|
+
msg = description if description else f"Starting {self.service_name}"
|
|
122
|
+
self.log(level=LogLevel.INFO, description=msg, resource=AbstractResource.SERVICEMON, action=Action.EXECUTE,
|
|
123
|
+
progress_status=ProgressStatus.IN_PROGRESS)
|
|
124
|
+
|
|
125
|
+
def end(self, status: ProgressStatus = ProgressStatus.DONE) -> Dict[str, Any]:
|
|
126
|
+
"""
|
|
127
|
+
End monitoring and record final metrics.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
status: The final status of the service execution
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Dict containing metrics summary
|
|
134
|
+
"""
|
|
135
|
+
# Calculate duration
|
|
136
|
+
end_time = time.time()
|
|
137
|
+
if self._start_time:
|
|
138
|
+
duration_ms = int((end_time - self._start_time) * 1000)
|
|
139
|
+
self._metrics["duration_ms"] = duration_ms
|
|
140
|
+
|
|
141
|
+
# Update metrics
|
|
142
|
+
self._metrics["end_time"] = datetime.now(timezone.utc).isoformat()
|
|
143
|
+
self._metrics["status"] = status.name
|
|
144
|
+
|
|
145
|
+
# Determine log level based on metrics
|
|
146
|
+
if self._metrics["errors"] > 0:
|
|
147
|
+
level = LogLevel.ERROR
|
|
148
|
+
if status == ProgressStatus.DONE:
|
|
149
|
+
status = ProgressStatus.FINISHED_WITH_ISSUES
|
|
150
|
+
elif self._metrics["warnings"] > 0:
|
|
151
|
+
level = LogLevel.WARNING
|
|
152
|
+
if status == ProgressStatus.DONE:
|
|
153
|
+
status = ProgressStatus.DONE_WITH_WARNINGS
|
|
154
|
+
else:
|
|
155
|
+
level = LogLevel.INFO
|
|
156
|
+
|
|
157
|
+
# Prepare summary message
|
|
158
|
+
summary_msg = (
|
|
159
|
+
f"Service {self.service_name} completed with status {status.name}. "
|
|
160
|
+
f"Duration: {self._metrics['duration_ms']}ms. "
|
|
161
|
+
f"Errors: {self._metrics['errors']}, Warnings: {self._metrics['warnings']}"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Log the completion
|
|
165
|
+
self.log(
|
|
166
|
+
level=level,
|
|
167
|
+
description=summary_msg,
|
|
168
|
+
resource=AbstractResource.SERVICEMON,
|
|
169
|
+
action=Action.EXECUTE,
|
|
170
|
+
progress_status=status,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return self._metrics
|
|
174
|
+
|
|
175
|
+
def log(self,
|
|
176
|
+
level: LogLevel,
|
|
177
|
+
description: str,
|
|
178
|
+
resource: Optional[Resource] = None,
|
|
179
|
+
source: Optional[str] = None,
|
|
180
|
+
destination: Optional[str] = None,
|
|
181
|
+
action: Optional[Action] = None,
|
|
182
|
+
progress_status: Optional[ProgressStatus] = None,
|
|
183
|
+
alert: Optional[Alert] = None,
|
|
184
|
+
e: Optional[Exception] = None,
|
|
185
|
+
systems_impacted: Optional[str] = None,
|
|
186
|
+
notes: Optional[str] = None,
|
|
187
|
+
**kwargs) -> None:
|
|
188
|
+
"""
|
|
189
|
+
Log a message with structured context.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
level: Log level
|
|
193
|
+
description: Log message
|
|
194
|
+
resource: Resource being accessed
|
|
195
|
+
action: Action being performed
|
|
196
|
+
progress_status: Current progress status
|
|
197
|
+
alert: Alert type if applicable
|
|
198
|
+
e: Exception if logging an error
|
|
199
|
+
**kwargs: Additional fields to include in the log
|
|
200
|
+
"""
|
|
201
|
+
# Update metrics
|
|
202
|
+
if level in (LogLevel.ERROR, LogLevel.CRITICAL):
|
|
203
|
+
self._metrics["errors"] += 1
|
|
204
|
+
elif level == LogLevel.WARNING:
|
|
205
|
+
self._metrics["warnings"] += 1
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
formatted_notes = f"{notes} ;elapsed_ms: {int((time.time() - self._start_time) * 1000)} " + str(kwargs)
|
|
209
|
+
# Create structured log
|
|
210
|
+
log = StructLog(
|
|
211
|
+
level=level,
|
|
212
|
+
resource=resource,
|
|
213
|
+
action=action,
|
|
214
|
+
progress_status=progress_status,
|
|
215
|
+
alert=alert,
|
|
216
|
+
e=e,
|
|
217
|
+
source=source,
|
|
218
|
+
destination=destination,
|
|
219
|
+
description=description,
|
|
220
|
+
collector_id=self.id,
|
|
221
|
+
base_context=self.base_context,
|
|
222
|
+
context=self.current_context,
|
|
223
|
+
systems_impacted=systems_impacted,
|
|
224
|
+
note=formatted_notes,
|
|
225
|
+
**kwargs
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Add service-specific fields
|
|
229
|
+
log_dict = log.to_dict()
|
|
230
|
+
|
|
231
|
+
# Write to logger
|
|
232
|
+
if level.value >= LogLevel.ERROR.value:
|
|
233
|
+
self._logger.error(log_dict)
|
|
234
|
+
elif level.value >= LogLevel.WARNING.value:
|
|
235
|
+
self._logger.warning(log_dict)
|
|
236
|
+
elif level.value >= LogLevel.INFO.value:
|
|
237
|
+
self._logger.info(log_dict)
|
|
238
|
+
else:
|
|
239
|
+
self._logger.debug(log_dict)
|
|
240
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: ipulse_shared_core_ftredge
|
|
3
|
-
Version: 6.1
|
|
3
|
+
Version: 6.3.1
|
|
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
|
|
@@ -8,7 +8,7 @@ License-File: LICENCE
|
|
|
8
8
|
Requires-Dist: pydantic[email]~=2.5
|
|
9
9
|
Requires-Dist: python-dateutil~=2.8
|
|
10
10
|
Requires-Dist: pytest~=7.1
|
|
11
|
-
Requires-Dist: ipulse_shared_base_ftredge>=5.
|
|
11
|
+
Requires-Dist: ipulse_shared_base_ftredge>=5.2.1
|
|
12
12
|
Dynamic: author
|
|
13
13
|
Dynamic: home-page
|
|
14
14
|
Dynamic: requires-dist
|
|
@@ -24,5 +24,7 @@ src/ipulse_shared_core_ftredge/models/user_profile.py
|
|
|
24
24
|
src/ipulse_shared_core_ftredge/models/user_profile_update.py
|
|
25
25
|
src/ipulse_shared_core_ftredge/models/user_status.py
|
|
26
26
|
src/ipulse_shared_core_ftredge/services/__init__.py
|
|
27
|
+
src/ipulse_shared_core_ftredge/services/base_exceptions.py
|
|
27
28
|
src/ipulse_shared_core_ftredge/services/base_firestore_service.py
|
|
28
|
-
src/ipulse_shared_core_ftredge/services/
|
|
29
|
+
src/ipulse_shared_core_ftredge/services/fastapiservicemon.py
|
|
30
|
+
src/ipulse_shared_core_ftredge/services/servicemon.py
|
|
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
|