Skip to content

Commit 57a97ed

Browse files
committed
polish logging in actor
1 parent c87451a commit 57a97ed

2 files changed

Lines changed: 84 additions & 59 deletions

File tree

src/apify/_actor.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -158,40 +158,47 @@ async def __aenter__(self) -> Self:
158158
This method must be called exactly once per Actor instance. Re-initializing an Actor or having multiple
159159
active Actor instances is not standard usage and may lead to warnings or unexpected behavior.
160160
"""
161-
self.log.info('Initializing Actor...')
162-
self.log.info('System info', extra=get_system_info())
163-
164161
if self._is_initialized:
165162
raise RuntimeError('The Actor was already initialized!')
166163

167-
if _ActorType._is_any_instance_initialized:
168-
self.log.warning('Repeated Actor initialization detected - this is non-standard usage, proceed with care.')
169-
164+
# Initialize configuration first - it's required for the next steps.
170165
if self._configuration:
171-
# Set explicitly the configuration in the service locator.
166+
# User provided explicit configuration - register it in the service locator.
172167
service_locator.set_configuration(self.configuration)
173168
else:
174-
# Ensure that the configuration (cached property) is set.
169+
# No explicit configuration provided - trigger creation of default configuration.
175170
_ = self.configuration
176171

177-
# Make sure that the currently initialized instance is also available through the global `Actor` proxy.
178-
cast('Proxy', Actor).__wrapped__ = self
172+
# Configure logging based on the configuration, any logs before this point are lost.
173+
if self._configure_logging:
174+
_configure_logging()
175+
self.log.debug('Logging configured')
179176

177+
self.log.info('Initializing Actor', extra=get_system_info())
178+
self.log.debug('Configuration initialized')
179+
180+
# Warn about non-standard usage patterns.
181+
if _ActorType._is_any_instance_initialized:
182+
self.log.warning('Repeated Actor initialization detected - this is non-standard usage, proceed with care.')
183+
184+
# Update the global Actor proxy to refer to this instance.
185+
cast('Proxy', Actor).__wrapped__ = self
180186
self._is_exiting = False
181187
self._was_final_persist_state_emitted = False
182188

183-
service_locator.set_event_manager(self.event_manager)
184-
185-
# Initialize storage client to ensure it's available in service locator.
189+
# Initialize the storage client and register it in the service locator.
186190
_ = self._storage_client
191+
self.log.debug('Storage client initialized')
187192

188-
# The logging configuration has to be called after all service_locator set methods.
189-
if self._configure_logging:
190-
_configure_logging()
191-
193+
# Initialize the event manager and register it in the service locator.
192194
await self.event_manager.__aenter__()
195+
self.log.debug('Event manager initialized')
196+
197+
# Initialize the charging manager.
193198
await self._charging_manager_implementation.__aenter__()
199+
self.log.debug('Charging manager initialized')
194200

201+
# Mark initialization as complete and update global state.
195202
self._is_initialized = True
196203
_ActorType._is_any_instance_initialized = True
197204
return self
@@ -334,7 +341,7 @@ def event_manager(self) -> EventManager:
334341
335342
It uses `ApifyEventManager` on the Apify platform and `LocalEventManager` otherwise.
336343
"""
337-
return (
344+
event_manager = (
338345
ApifyEventManager(
339346
configuration=self.configuration,
340347
persist_state_interval=self.configuration.persist_state_interval,
@@ -345,6 +352,8 @@ def event_manager(self) -> EventManager:
345352
persist_state_interval=self.configuration.persist_state_interval,
346353
)
347354
)
355+
service_locator.set_event_manager(event_manager)
356+
return event_manager
348357

349358
@cached_property
350359
def _charging_manager_implementation(self) -> ChargingManagerImplementation:

tests/unit/actor/test_actor_log.py

Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -36,61 +36,77 @@ async def test_actor_logs_messages_correctly(caplog: pytest.LogCaptureFixture) -
3636
# Test that exception in Actor.main is logged with the traceback
3737
raise RuntimeError('Dummy RuntimeError')
3838

39-
# Updated expected number of log records (an extra record is now captured)
40-
assert len(caplog.records) == 12
39+
# Updated expected number of log records (additional debug messages added)
40+
assert len(caplog.records) == 16
4141

42-
# Record 0: Extra Pytest context log
42+
# Record 0: First Pytest context log
4343
assert caplog.records[0].levelno == logging.DEBUG
4444
assert caplog.records[0].message.startswith('Running in Pytest')
4545

4646
# Record 1: Duplicate Pytest context log
4747
assert caplog.records[1].levelno == logging.DEBUG
48-
assert caplog.records[0].message.startswith('Running in Pytest')
48+
assert caplog.records[1].message.startswith('Running in Pytest')
4949

50-
# Record 2: Initializing Actor...
51-
assert caplog.records[2].levelno == logging.INFO
52-
assert caplog.records[2].message == 'Initializing Actor...'
50+
# Record 2: Logging configured
51+
assert caplog.records[2].levelno == logging.DEBUG
52+
assert caplog.records[2].message == 'Logging configured'
5353

54-
# Record 3: System info
54+
# Record 3: Initializing Actor
5555
assert caplog.records[3].levelno == logging.INFO
56-
assert caplog.records[3].message == 'System info'
56+
assert caplog.records[3].message == 'Initializing Actor'
5757

58-
# Record 4: Event manager initialized
58+
# Record 4: Configuration initialized
5959
assert caplog.records[4].levelno == logging.DEBUG
60-
assert caplog.records[4].message == 'Debug message'
60+
assert caplog.records[4].message == 'Configuration initialized'
6161

62-
# Record 7: Info message
63-
assert caplog.records[5].levelno == logging.INFO
64-
assert caplog.records[5].message == 'Info message'
62+
# Record 5: Storage client initialized
63+
assert caplog.records[5].levelno == logging.DEBUG
64+
assert caplog.records[5].message == 'Storage client initialized'
6565

66-
# Record 8: Warning message
67-
assert caplog.records[6].levelno == logging.WARNING
68-
assert caplog.records[6].message == 'Warning message'
66+
# Record 6: Event manager initialized
67+
assert caplog.records[6].levelno == logging.DEBUG
68+
assert caplog.records[6].message == 'Event manager initialized'
6969

70-
# Record 9: Error message
71-
assert caplog.records[7].levelno == logging.ERROR
72-
assert caplog.records[7].message == 'Error message'
70+
# Record 7: Charging manager initialized
71+
assert caplog.records[7].levelno == logging.DEBUG
72+
assert caplog.records[7].message == 'Charging manager initialized'
7373

74-
# Record 10: Exception message with traceback (ValueError)
75-
assert caplog.records[8].levelno == logging.ERROR
76-
assert caplog.records[8].message == 'Exception message'
77-
assert caplog.records[8].exc_info is not None
78-
assert caplog.records[8].exc_info[0] is ValueError
79-
assert isinstance(caplog.records[8].exc_info[1], ValueError)
80-
assert str(caplog.records[8].exc_info[1]) == 'Dummy ValueError'
74+
# Record 8: Debug message
75+
assert caplog.records[8].levelno == logging.DEBUG
76+
assert caplog.records[8].message == 'Debug message'
8177

82-
# Record 11: Multiline log message
78+
# Record 9: Info message
8379
assert caplog.records[9].levelno == logging.INFO
84-
assert caplog.records[9].message == 'Multi\nline\nlog\nmessage'
85-
86-
# Record 12: Actor failed with an exception (RuntimeError)
87-
assert caplog.records[10].levelno == logging.ERROR
88-
assert caplog.records[10].message == 'Actor failed with an exception'
89-
assert caplog.records[10].exc_info is not None
90-
assert caplog.records[10].exc_info[0] is RuntimeError
91-
assert isinstance(caplog.records[10].exc_info[1], RuntimeError)
92-
assert str(caplog.records[10].exc_info[1]) == 'Dummy RuntimeError'
93-
94-
# Record 13: Exiting Actor
95-
assert caplog.records[11].levelno == logging.INFO
96-
assert caplog.records[11].message == 'Exiting Actor'
80+
assert caplog.records[9].message == 'Info message'
81+
82+
# Record 10: Warning message
83+
assert caplog.records[10].levelno == logging.WARNING
84+
assert caplog.records[10].message == 'Warning message'
85+
86+
# Record 11: Error message
87+
assert caplog.records[11].levelno == logging.ERROR
88+
assert caplog.records[11].message == 'Error message'
89+
90+
# Record 12: Exception message with traceback (ValueError)
91+
assert caplog.records[12].levelno == logging.ERROR
92+
assert caplog.records[12].message == 'Exception message'
93+
assert caplog.records[12].exc_info is not None
94+
assert caplog.records[12].exc_info[0] is ValueError
95+
assert isinstance(caplog.records[12].exc_info[1], ValueError)
96+
assert str(caplog.records[12].exc_info[1]) == 'Dummy ValueError'
97+
98+
# Record 13: Multiline log message
99+
assert caplog.records[13].levelno == logging.INFO
100+
assert caplog.records[13].message == 'Multi\nline\nlog\nmessage'
101+
102+
# Record 14: Actor failed with an exception (RuntimeError)
103+
assert caplog.records[14].levelno == logging.ERROR
104+
assert caplog.records[14].message == 'Actor failed with an exception'
105+
assert caplog.records[14].exc_info is not None
106+
assert caplog.records[14].exc_info[0] is RuntimeError
107+
assert isinstance(caplog.records[14].exc_info[1], RuntimeError)
108+
assert str(caplog.records[14].exc_info[1]) == 'Dummy RuntimeError'
109+
110+
# Record 15: Exiting Actor
111+
assert caplog.records[15].levelno == logging.INFO
112+
assert caplog.records[15].message == 'Exiting Actor'

0 commit comments

Comments
 (0)