From 1221ffab9f460f557db116160c8c6f8c20540496 Mon Sep 17 00:00:00 2001 From: Fang Zheng Date: Thu, 15 Oct 2026 10:04:58 -0700 Subject: [PATCH] feat: add event_log_level config to control per-event logging Co-Authored-By: Claude Opus 5.5 --- src/amplitude/__init__.py | 2 +- src/amplitude/config.py | 6 +++++- src/amplitude/constants.py | 6 ++++++ src/amplitude/processor.py | 7 ++++++- src/test/test_processor.py | 30 +++++++++++++++++++++++++++++- 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/amplitude/__init__.py b/src/amplitude/__init__.py index dbb5a80..b806214 100644 --- a/src/amplitude/__init__.py +++ b/src/amplitude/__init__.py @@ -5,5 +5,5 @@ from amplitude.event import BaseEvent, EventOptions, Identify, Revenue, IdentifyEvent, \ GroupIdentifyEvent, RevenueEvent, Plan, IngestionMetadata from amplitude.config import Config -from amplitude.constants import PluginType +from amplitude.constants import PluginType, EventLogLevel from amplitude.plugin import EventPlugin, DestinationPlugin diff --git a/src/amplitude/config.py b/src/amplitude/config.py index 78fa2db..4be0893 100644 --- a/src/amplitude/config.py +++ b/src/amplitude/config.py @@ -34,6 +34,8 @@ class Config: Provide storage instance for events buffer. plan (amplitude.event.Plan, optional): Tracking plan information. Default to None. ingestion_metadata (amplitude.event.IngestionMetadata, optional): Ingestion metadata. Default to None. + event_log_level (amplitude.constants.EventLogLevel, optional): Level used to log each event after it is + sent or dropped. EventLogLevel.NONE disables these logs. Default to EventLogLevel.INFO. Properties: options: A dictionary contains minimum id length information. None if min_id_length not set. @@ -57,7 +59,8 @@ def __init__(self, api_key: str = None, server_url: Optional[str] = None, storage_provider: Optional[StorageProvider] = None, plan: Plan = None, - ingestion_metadata: IngestionMetadata = None): + ingestion_metadata: IngestionMetadata = None, + event_log_level: constants.EventLogLevel = constants.EventLogLevel.INFO): """The constructor of Config class""" self.api_key: str = api_key self._flush_queue_size: int = flush_queue_size @@ -74,6 +77,7 @@ def __init__(self, api_key: str = None, self.opt_out: bool = False self.plan: Plan = plan self.ingestion_metadata: IngestionMetadata = ingestion_metadata + self.event_log_level: constants.EventLogLevel = event_log_level def get_storage(self) -> Storage: """Use configured StorageProvider to create a Storage instance then return. diff --git a/src/amplitude/constants.py b/src/amplitude/constants.py index 75b72ad..24e050e 100644 --- a/src/amplitude/constants.py +++ b/src/amplitude/constants.py @@ -57,3 +57,9 @@ class PluginType(Enum): ENRICHMENT = 1 DESTINATION = 2 OBSERVE = 3 + + +class EventLogLevel(Enum): + NONE = 0 + DEBUG = 1 + INFO = 2 diff --git a/src/amplitude/processor.py b/src/amplitude/processor.py index 794ffc4..554e509 100644 --- a/src/amplitude/processor.py +++ b/src/amplitude/processor.py @@ -1,3 +1,4 @@ +from amplitude.constants import EventLogLevel from amplitude.exception import InvalidAPIKeyError from amplitude.http_client import HttpStatus @@ -83,5 +84,9 @@ def callback(self, events, code, message): self.configuration.logger.exception(f"Error callback for event {event}") def log(self, events, code, message): + level = self.configuration.event_log_level + if level == EventLogLevel.NONE: + return + log_fn = self.configuration.logger.debug if level == EventLogLevel.DEBUG else self.configuration.logger.info for event in events: - self.configuration.logger.info(message, extra={'code':code, 'event':event}) \ No newline at end of file + log_fn(message, extra={'code':code, 'event':event}) \ No newline at end of file diff --git a/src/test/test_processor.py b/src/test/test_processor.py index dba4da5..15beb3f 100644 --- a/src/test/test_processor.py +++ b/src/test/test_processor.py @@ -1,7 +1,7 @@ import unittest from unittest.mock import MagicMock -from amplitude import Config, BaseEvent +from amplitude import Config, BaseEvent, EventLogLevel from amplitude.http_client import Response, HttpStatus from amplitude.processor import ResponseProcessor @@ -25,6 +25,34 @@ def test_process_response_with_unknown_status_retries_events(self): configuration.callback.assert_not_called() event_callback.assert_not_called() + def _process_success(self, configuration): + configuration.logger = MagicMock() + processor = ResponseProcessor() + processor.setup(configuration, MagicMock()) + event = BaseEvent("test_event", "test_user") + processor.process_response(Response(HttpStatus.SUCCESS), [event]) + return event + + def test_log_event_at_info_level_by_default(self): + configuration = Config() + event = self._process_success(configuration) + configuration.logger.info.assert_called_once_with( + "Event sent successfully.", extra={'code': 200, 'event': event}) + configuration.logger.debug.assert_not_called() + + def test_log_event_at_debug_level(self): + configuration = Config(event_log_level=EventLogLevel.DEBUG) + event = self._process_success(configuration) + configuration.logger.debug.assert_called_once_with( + "Event sent successfully.", extra={'code': 200, 'event': event}) + configuration.logger.info.assert_not_called() + + def test_log_event_disabled(self): + configuration = Config(event_log_level=EventLogLevel.NONE) + self._process_success(configuration) + configuration.logger.info.assert_not_called() + configuration.logger.debug.assert_not_called() + if __name__ == '__main__': unittest.main()