Skip to content

Commit fe77772

Browse files
committedNov 29, 2023
Fixes #68: fix filename in Timed mode when hitting size-based
rollover more than once per the timed interval. Thanks @moynihan This also ensures the unit tests detect this condition.
1 parent 1fdd7d9 commit fe77772

File tree

3 files changed

+50
-14
lines changed

3 files changed

+50
-14
lines changed
 

‎src/concurrent_log_handler/__init__.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -819,12 +819,13 @@ def doRollover(self): # noqa: C901, PLR0912
819819

820820
gzip_ext = ".gz" if self.clh.use_gzip else ""
821821

822-
counter = 0
823-
while os.path.exists(dfn + gzip_ext):
824-
counter += 1
825-
ending = f".{counter - 1}{gzip_ext}"
826-
if dfn.endswith(ending):
827-
dfn = dfn[: -len(ending)]
822+
counter = 1
823+
if os.path.exists(dfn + gzip_ext):
824+
while os.path.exists(f"{dfn}.{counter}{gzip_ext}"):
825+
ending = f".{counter - 1}{gzip_ext}"
826+
if dfn.endswith(ending):
827+
dfn = dfn[: -len(ending)]
828+
counter += 1
828829
dfn = f"{dfn}.{counter}"
829830

830831
# if os.path.exists(dfn):
@@ -840,8 +841,7 @@ def doRollover(self): # noqa: C901, PLR0912
840841
# Thanks to @moynihan
841842
for file in self.getFilesToDelete():
842843
os.remove(file)
843-
# if not self.delay:
844-
# self.stream = self._open()
844+
845845
newRolloverAt = self.computeRollover(currentTime)
846846
while newRolloverAt <= currentTime:
847847
newRolloverAt = newRolloverAt + self.interval

‎tests/stresstest.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import multiprocessing
2020
import os
2121
import random
22+
import re
2223
import string
2324
import time
2425
from dataclasses import dataclass, field
@@ -254,6 +255,19 @@ def run_stress_test(test_opts: TestOptions) -> int:
254255
f"{rollover_counter.get_value()} - min was {test_opts.min_rollovers})"
255256
)
256257

258+
log_path = os.path.join(test_opts.log_dir, test_opts.log_file)
259+
all_log_files = glob.glob(f"{log_path}*")
260+
261+
gzip_ext = "[.]gz" if test_opts.log_opts["use_gzip"] else ""
262+
263+
# Issue #68 - check for incorrect naming of files when using TimedRotatingFileHandler
264+
# and we had to rollover more often than the normal `when` interval due to size limits
265+
# If this happens more than once per interval, the names would be like "logfile.2020-01-01.1.2.3.4.5.gz"
266+
for log_file in all_log_files:
267+
if re.search(r"\.\d+\.\d+" + gzip_ext, str(log_file)):
268+
print(f"Error: Incorrect naming of log file: {log_file}")
269+
return 1
270+
257271
# If backupCount is less than 10, assume we want specifically to
258272
# test backupCount. This means, in most cases, we will have deleted
259273
# some logs files and we should not expect to find all log files.
@@ -264,13 +278,11 @@ def run_stress_test(test_opts: TestOptions) -> int:
264278
backup_count = test_opts.log_opts.get("backupCount", 0)
265279
if backup_count > 0 and backup_count < MAGIC_BACKUP_COUNT:
266280
expect_all_lines = False
267-
log_path = os.path.join(test_opts.log_dir, test_opts.log_file)
268-
log_files = glob.glob(f"{log_path}*")
269281
# Check that backupCount was not exceeded.
270282
# The +1 is because we're counting 'backups' plus the main log file.
271-
if len(log_files) != backup_count + 1:
283+
if len(all_log_files) != backup_count + 1:
272284
print(
273-
f"Error: {len(log_files)} log files were created but "
285+
f"Error: {len(all_log_files)} log files were created but "
274286
f"we expected {backup_count + 1}. Could indicate a failure "
275287
f"to rotate properly or to delete excessive backups (`backupCount`)."
276288
)

‎tests/test_stresstest.py

+26-2
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,24 @@
102102
}
103103
),
104104
),
105-
"backupCount=5, use_timed=True, maxBytes=1KiB, interval=2, log_calls=1_000, use_gzip=True": TestOptions(
105+
# This checks the issue in Issue #68 - in Timed mode, when we have to rollover more
106+
# often than the interval due to size limits, the naming of the files is incorrect.
107+
# The check for the incorrect names is done in `run_stress_test()`. The following case can
108+
# also check it.
109+
"use_timed=True, maxBytes=512B, interval=3, log_calls=2_000, use_gzip=True, num_processes=3": TestOptions(
110+
use_timed=True,
111+
log_calls=2_000,
112+
min_rollovers=5,
113+
num_processes=3,
114+
log_opts=TestOptions.default_timed_log_opts(
115+
{
116+
"maxBytes": 512,
117+
"interval": 3,
118+
"use_gzip": True,
119+
}
120+
),
121+
),
122+
"backupCount=5, use_timed=True, maxBytes=1KiB, interval=5, log_calls=1_000, use_gzip=True, debug=True": TestOptions(
106123
use_timed=True,
107124
log_calls=1_000,
108125
min_rollovers=5,
@@ -156,7 +173,14 @@
156173
}
157174

158175

159-
@pytest.mark.parametrize("label, test_opts", TEST_CASES.items())
176+
use_timed_only = False
177+
178+
test_cases = TEST_CASES
179+
if use_timed_only:
180+
test_cases = {label: case for label, case in TEST_CASES.items() if case.use_timed}
181+
182+
183+
@pytest.mark.parametrize("label, test_opts", test_cases.items())
160184
def test_run_stress_test(label: str, test_opts: TestOptions): # noqa: ARG001
161185
"""Run the stress test with the given options and verify the result."""
162186
assert run_stress_test(test_opts) == (1 if test_opts.induce_failure else 0)

0 commit comments

Comments
 (0)
Please sign in to comment.