poll_reporter
¶
Logging for loops that poll every half second, where a line per cycle would bury the journal.
The REST pollers in bin/<broker>/ and the combiners in bin/unified/ report each cycle's outcome - "Merged 0 of 0
order(s)", "Orders poll failed: ..." - and at two cycles a second that is about 170,000 lines a day per script, nearly
all of them the same line. PollReporter logs a message when it differs from the last one it logged at that level, and
otherwise repeats it once every interval seconds, so the journal shows every change and a steady heartbeat while
nothing changes.
Classes:
| Name | Description |
|---|---|
PollReporter |
Log a polling loop's outcomes when they change, and at most once every |
PollReporter
¶
Log a polling loop's outcomes when they change, and at most once every interval seconds while they do not.
loggeris where the messages go.intervalis how often an unchanged message is repeated, in seconds.
Start with no message logged at any level, so the first message at each level is always logged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
logger
|
Logger
|
Where the messages go. |
required |
interval
|
float
|
How often an unchanged message is repeated, in seconds; 60 unless given. |
60
|
Example programs
"""Reports ten polling cycles, logging only the cycles whose outcome differs from the last one logged.
The REST pollers and the unified combiners run a cycle twice a second. Logging every cycle's outcome would write about 170,000 nearly identical lines a day per script, so they report through a `PollReporter`, which logs a message only when it differs from the last message logged at that level, or when that message is at least `interval` seconds old.
This program feeds a reporter the outcomes of ten made-up order poll cycles. It uses the default one-minute interval, and the ten cycles take far less than a minute, so no unchanged message is repeated. The reporter's logger writes to the output through a handler that prints the level and message without a timestamp. Nothing is polled.
Notice that the three identical "Merged 0" cycles produce one line, that the error is logged as soon as it appears and again when its text changes, and that the INFO line after the errors is logged because it differs from the last INFO line, not because an error came between.
Run it from the project root:
python examples/utilities/poll_reporter/PollReporter/example_1_quiet_until_something_changes.py
"""
import logging
import sys
from utilities.poll_reporter import (
PollReporter,
)
class QuietUntilSomethingChangesExample:
"""Reports ten cycles through a reporter and prints what was logged.
Attributes:
reporter (PollReporter): The reporter being shown.
outcomes (list): Each cycle's level name and message.
"""
def __init__(self):
"""Builds a logger that prints to the output and a reporter over it.
Returns:
None: This method returns nothing.
"""
logger = logging.getLogger('example.orders_poller')
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter(' logged %(levelname)s: %(message)s'))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
self.reporter = PollReporter(logger)
self.outcomes = [
(
'info',
'Merged 0 of 0 order(s)',
),
(
'info',
'Merged 0 of 0 order(s)',
),
(
'info',
'Merged 0 of 0 order(s)',
),
(
'info',
'Merged 2 of 2 order(s)',
),
(
'error',
'Orders poll failed: 503 Service Unavailable',
),
(
'error',
'Orders poll failed: 503 Service Unavailable',
),
(
'error',
'Orders poll failed: read timed out',
),
(
'info',
'Merged 0 of 2 order(s)',
),
(
'info',
'Merged 0 of 2 order(s)',
),
(
'info',
'Merged 0 of 2 order(s)',
),
]
def run(self):
"""Reports each cycle.
Returns:
None: This method returns nothing.
"""
print(f'Repeat interval: {self.reporter.interval} seconds')
for cycle, outcome in enumerate(self.outcomes, start=1):
level_name, message = outcome
print(f'cycle {cycle}: {level_name} {message}')
if level_name == 'info':
self.reporter.info(message)
else:
self.reporter.error(message)
if __name__ == '__main__':
QuietUntilSomethingChangesExample().run()
It prints:
Repeat interval: 60 seconds
cycle 1: info Merged 0 of 0 order(s)
logged INFO: Merged 0 of 0 order(s)
cycle 2: info Merged 0 of 0 order(s)
cycle 3: info Merged 0 of 0 order(s)
cycle 4: info Merged 2 of 2 order(s)
logged INFO: Merged 2 of 2 order(s)
cycle 5: error Orders poll failed: 503 Service Unavailable
logged ERROR: Orders poll failed: 503 Service Unavailable
cycle 6: error Orders poll failed: 503 Service Unavailable
cycle 7: error Orders poll failed: read timed out
logged ERROR: Orders poll failed: read timed out
cycle 8: info Merged 0 of 2 order(s)
logged INFO: Merged 0 of 2 order(s)
cycle 9: info Merged 0 of 2 order(s)
cycle 10: info Merged 0 of 2 order(s)
"""Shows the heartbeat: an unchanged message is logged again once the interval has passed, and each level keeps its own memory.
A `PollReporter` remembers the last message it logged at each level and when. An unchanged message is logged again once `interval` seconds have passed since it was last logged, so the journal shows the loop is still alive. Levels are tracked separately, so `log` with `logging.WARNING` does not reset what `info` remembers.
To keep the program short, the interval is 0.5 seconds and the program sleeps 0.6 seconds between two groups of cycles, instead of the pollers' 60 seconds. The logger writes to the output through a handler that prints the level and message without a timestamp. Nothing is polled.
Notice that the second group repeats "Positions unchanged" once, right after the pause, and then goes quiet again, and that the WARNING and INFO lines do not silence each other.
Run it from the project root:
python examples/utilities/poll_reporter/PollReporter/example_2_heartbeat_after_the_interval.py
"""
import logging
import sys
import time
from utilities.poll_reporter import (
PollReporter,
)
class HeartbeatAfterTheIntervalExample:
"""Reports two groups of cycles with a pause longer than the interval between them.
Attributes:
reporter (PollReporter): The reporter being shown.
"""
def __init__(self):
"""Builds a logger that prints to the output and a reporter with a 0.5 second interval.
Returns:
None: This method returns nothing.
"""
logger = logging.getLogger('example.positions_poller')
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter(' logged %(levelname)s: %(message)s'))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
self.reporter = PollReporter(logger, interval=0.5)
def one_group(self, label):
"""Reports three unchanged cycles with a warning between them.
Args:
label (str): The group's name, for the printout.
Returns:
None: This method returns nothing.
"""
print(label)
self.reporter.info('Positions unchanged')
self.reporter.log(logging.WARNING, 'Dhan answered slowly')
self.reporter.info('Positions unchanged')
self.reporter.log(logging.WARNING, 'Dhan answered slowly')
self.reporter.info('Positions unchanged')
def run(self):
"""Reports two groups with a pause between them.
Returns:
None: This method returns nothing.
"""
self.one_group('First group')
time.sleep(0.6)
self.one_group('Second group, after 0.6 seconds')
levels = []
for level in sorted(self.reporter.last):
levels.append(logging.getLevelName(level))
print(f'Levels remembered: {levels}')
if __name__ == '__main__':
HeartbeatAfterTheIntervalExample().run()
It prints:
Methods:
| Name | Description |
|---|---|
log |
Log |
info |
Log a cycle's outcome at INFO, when it changed or once an interval. |
error |
Log a failed cycle at ERROR, when the failure changed or once an interval. |
Source code in utilities/poll_reporter.py
log
¶
Log message at level when it is not the message last logged at that level, or that one is interval old.
levelis alogginglevel, for examplelogging.INFO.messageis the text to log.
Example usage
self.reporter.log(logging.WARNING, 'Dhan answered slowly')
Source code in utilities/poll_reporter.py
info
¶
Log a cycle's outcome at INFO, when it changed or once an interval. message is the text.
Example usage
error
¶
Log a failed cycle at ERROR, when the failure changed or once an interval. message is the text.
Example usage