Skip to content

Commit d9e4dd2

Browse files
sakshamg1304rohitesh-wingify
authored andcommittedMar 13, 2025·
feat: send error logs to server
1 parent 5d0f121 commit d9e4dd2

11 files changed

+139
-12
lines changed
 

‎CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [1.5.0] - 2025-03-12
8+
9+
### Added
10+
11+
- Added support for sending error logs to VWO server for better debugging.
12+
713
## [1.4.0] - 2024-11-22
814

915
### Added

‎setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def run(self):
121121

122122
setup(
123123
name="vwo-fme-python-sdk",
124-
version="1.3.0",
124+
version="1.5.0",
125125
description="VWO Feature Management and Experimentation SDK for Python",
126126
long_description=long_description,
127127
long_description_content_type="text/markdown",

‎vwo/api/set_attribute_api.py

-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ def create_and_send_impression_for_attribute(
3434
context: ContextModel
3535
):
3636
properties = get_events_base_properties(
37-
settings,
3837
EventEnum.VWO_SYNC_VISITOR_PROP.value,
3938
visitor_user_agent=context.get_user_agent(),
4039
ip_address=context.get_ip_address()

‎vwo/api/track_api.py

-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ def create_and_send_impression_for_track(
6363
:param event_properties: The properties of the event.
6464
"""
6565
properties = get_events_base_properties(
66-
settings,
6766
event_name,
6867
visitor_user_agent=context.get_user_agent(),
6968
ip_address=context.get_ip_address()

‎vwo/constants/Constants.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class Constants:
1717
# Mock package_file equivalent
1818
package_file = {
1919
"name": "vwo-fme-python-sdk", # Replace with actual package name
20-
"version": "1.3.0", # Replace with actual package version
20+
"version": "1.5.0", # Replace with actual package version
2121
}
2222

2323
# Constants

‎vwo/enums/event_enum.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@
1717

1818
class EventEnum(Enum):
1919
VWO_VARIATION_SHOWN = 'vwo_variationShown'
20-
VWO_SYNC_VISITOR_PROP = 'vwo_syncVisitorProp'
20+
VWO_SYNC_VISITOR_PROP = 'vwo_syncVisitorProp'
21+
VWO_LOG_EVENT = 'vwo_log'

‎vwo/packages/logger/core/log_manager.py

+6
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@
2121
from ..logger import Logger
2222
import datetime
2323
from typing import Dict, Any, Union
24+
from ....enums.event_enum import EventEnum
25+
from ....constants.Constants import Constants
2426

2527
class LogManager(Logger):
2628
_instance = None
29+
stored_messages = set() # Set to store already logged messages for duplicate prevention
2730

2831
def __new__(cls, *args, **kwargs):
2932
if cls._instance is None:
@@ -84,4 +87,7 @@ def warn(self, message: str) -> None:
8487
self.transport_manager.log(LogLevelEnum.WARN, message)
8588

8689
def error(self, message: str) -> None:
90+
from ....utils.log_message_util import send_log_to_vwo
91+
# Log the error using the transport manager
8792
self.transport_manager.log(LogLevelEnum.ERROR, message)
93+
send_log_to_vwo(message, LogLevelEnum.ERROR)

‎vwo/services/settings_manager.py

+6
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ def __init__(self, options):
7575
@classmethod
7676
def get_instance(cls):
7777
return cls._instance
78+
79+
def get_account_id(self):
80+
return self.account_id
81+
82+
def get_sdk_key(self):
83+
return self.sdk_key
7884

7985
def fetch_settings_and_cache_in_storage(self, update=False):
8086
try:

‎vwo/utils/impression_util.py

-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ def create_and_send_impression_for_variation_shown(
2727
):
2828
# Get base properties for the event
2929
properties = get_events_base_properties(
30-
settings,
3130
EventEnum.VWO_VARIATION_SHOWN.value,
3231
visitor_user_agent=context.get_user_agent(),
3332
ip_address=context.get_ip_address()

‎vwo/utils/log_message_util.py

+35-1
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@
1717
import json
1818
import os
1919
from typing import Dict, Any
20+
from ..enums.event_enum import EventEnum
21+
from ..constants.Constants import Constants
2022

2123
# Determine the base directory (the directory containing this script)
2224
base_dir = os.path.dirname(__file__)
2325

26+
# Set to store already logged messages (to avoid duplicates)
27+
stored_messages = set()
28+
2429
def load_json_file(filename: str) -> Dict[str, str]:
2530
"""
2631
Loads a JSON file and returns its content as a dictionary.
@@ -37,4 +42,33 @@ def load_json_file(filename: str) -> Dict[str, str]:
3742
error_messages = load_json_file('error-messages.json')
3843
info_messages = load_json_file('info-message.json')
3944
trace_messages = load_json_file('trace-messages.json')
40-
warn_messages = load_json_file('warn-messages.json')
45+
warn_messages = load_json_file('warn-messages.json')
46+
47+
def send_log_to_vwo(message: str, message_type: str) -> None:
48+
"""
49+
Sends a log message to VWO.
50+
51+
:param message: The message to send.
52+
:param message_type: The type of message (e.g., ERROR, INFO).
53+
"""
54+
from ..utils.network_util import get_events_base_properties, get_messaging_event_payload, send_messaging_event
55+
56+
if os.getenv('TEST_ENV') == 'true':
57+
return # Skip logging in test environment
58+
59+
# Construct the message to check for duplicates
60+
message_to_send = f"{message}-{Constants.SDK_NAME}-{Constants.SDK_VERSION}"
61+
62+
# Avoid sending duplicate messages
63+
if message_to_send not in stored_messages:
64+
# Add the message to the stored set to prevent duplicates
65+
stored_messages.add(message_to_send)
66+
67+
# Get event properties for the error event
68+
properties = get_events_base_properties(EventEnum.VWO_LOG_EVENT.value)
69+
70+
# Create the payload for the messaging event
71+
payload = get_messaging_event_payload(message_type, message, EventEnum.VWO_LOG_EVENT.value)
72+
73+
# Send the message via HTTP request
74+
send_messaging_event(properties, payload)

‎vwo/utils/network_util.py

+82-5
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,21 @@ def get_event_batching_query_params(account_id: str) -> Dict[str, Any]:
6464

6565
# Function to build generic properties for tracking events
6666
def get_events_base_properties(
67-
setting: SettingsModel,
6867
event_name: str,
6968
visitor_user_agent: str = '',
7069
ip_address: str = ''
7170
) -> Dict[str, Any]:
72-
sdk_key = setting.get_sdk_key()
71+
from ..services.settings_manager import SettingsManager
72+
# Get the instance of SettingsManager
73+
settings = SettingsManager.get_instance()
74+
75+
# Fetch SDK key and account ID from the SettingsManager instance
76+
sdk_key = settings.get_sdk_key()
77+
account_id = settings.get_account_id()
78+
7379
return {
7480
'en': event_name,
75-
'a': setting.get_account_id(),
81+
'a': account_id,
7682
'env': sdk_key,
7783
'eTime': get_current_unix_timestamp_in_millis(),
7884
'random': get_random_number(),
@@ -90,8 +96,9 @@ def _get_event_base_payload(
9096
visitor_user_agent: str = '',
9197
ip_address: str = ''
9298
) -> Dict[str, Any]:
93-
uuid_value = get_uuid(user_id, settings.get_account_id())
94-
sdk_key = settings.get_sdk_key()
99+
from ..services.settings_manager import SettingsManager
100+
uuid_value = get_uuid(user_id, SettingsManager.get_instance().get_account_id())
101+
sdk_key = SettingsManager.get_instance().get_sdk_key()
95102
properties = {
96103
'd': {
97104
'msgId': f"{uuid_value}-{get_current_unix_timestamp_in_millis()}",
@@ -268,6 +275,76 @@ def send_post_api_request(properties: Dict[str, Any], payload: Dict[str, Any]):
268275
),
269276
)
270277

278+
# Function to construct the messaging event payload
279+
def get_messaging_event_payload(message_type: str, message: str, event_name: str) -> Dict[str, Any]:
280+
from ..services.settings_manager import SettingsManager
281+
# Get user ID and properties
282+
settings = SettingsManager.get_instance()
283+
user_id = f"{settings.get_account_id()}_{settings.get_sdk_key()}"
284+
properties = _get_event_base_payload(None, user_id, event_name, None, None)
285+
286+
# Set the environment key and product
287+
properties['d']['event']['props']['vwo_envKey'] = settings.get_sdk_key()
288+
properties['d']['event']['props']['product'] = "fme" # Assuming 'product' is a required field
289+
290+
# Set the message data
291+
data = {
292+
'type': message_type,
293+
'content': {
294+
'title': message,
295+
'dateTime': get_current_unix_timestamp_in_millis()
296+
}
297+
}
298+
299+
# Add data to the properties
300+
properties['d']['event']['props']['data'] = data
301+
302+
return properties
303+
304+
def send_messaging_event(properties: Dict[str, Any], payload: Dict[str, Any]) -> Dict[str, Any]:
305+
network_instance = NetworkManager.get_instance()
306+
307+
try:
308+
# Prepare the request model
309+
request = RequestModel(
310+
Constants.HOST_NAME,
311+
'POST',
312+
UrlEnum.EVENTS.value,
313+
properties,
314+
payload,
315+
None,
316+
Constants.HTTPS_PROTOCOL,
317+
443
318+
)
319+
320+
# Flag to check if the event loop is initialized
321+
event_loop_initialized = False
322+
main_event_loop = None
323+
324+
# Start a new event loop in a separate thread if it hasn't been initialized yet
325+
if not event_loop_initialized:
326+
main_event_loop = asyncio.new_event_loop()
327+
threading.Thread(target=start_event_loop, args=(main_event_loop,), daemon=True).start()
328+
event_loop_initialized = True
329+
330+
# Submit the asynchronous POST request to the newly started event loop
331+
asyncio.run_coroutine_threadsafe(network_instance.post_async(request), main_event_loop)
332+
333+
# Return a success message
334+
return {"success": True, "message": "Event sent successfully"}
335+
336+
except Exception as err:
337+
# Log the error
338+
LogManager.get_instance().error(
339+
error_messages.get('NETWORK_CALL_FAILED').format(
340+
method='POST',
341+
err=err,
342+
)
343+
)
344+
345+
# Return a failure message
346+
return {"success": False, "message": "Failed to send event"}
347+
271348
# Function to start the event loop in a new thread
272349
def start_event_loop(loop):
273350
# Set the provided loop as the current event loop for the new thread

0 commit comments

Comments
 (0)
Please sign in to comment.