From 4355defa6bc60384be9ac4d9875af70db65ebdcf Mon Sep 17 00:00:00 2001 From: john feng Date: Sun, 12 Jan 2025 23:15:22 -0800 Subject: [PATCH 1/7] add truncation logic to limit message in file logger --- src/core/src/local_loggers/FileLogger.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/core/src/local_loggers/FileLogger.py b/src/core/src/local_loggers/FileLogger.py index cc47a1137..5740787ea 100644 --- a/src/core/src/local_loggers/FileLogger.py +++ b/src/core/src/local_loggers/FileLogger.py @@ -38,8 +38,9 @@ def __del__(self): def write(self, message, fail_silently=True): try: + truncated_message = self.truncate_message(message) if self.log_file_handle is not None: - self.log_file_handle.write(message) + self.log_file_handle.write(truncated_message) except Exception as error: # DO NOT write any errors here to stdout failure_message = "Fatal exception trying to write to log file: " + repr(error) + ". Attempted message: " + str(message) @@ -50,9 +51,10 @@ def write(self, message, fail_silently=True): def write_irrecoverable_exception(self, message): """ A best-effort attempt to write out errors where writing to the primary log file was interrupted""" try: + truncated_message = self.truncate_message(message) with self.env_layer.file_system.open(self.log_failure_log_file, 'a+') as fail_log: timestamp = self.env_layer.datetime.timestamp() - fail_log.write("\n" + timestamp + "> " + message) + fail_log.write("\n" + timestamp + "> " + truncated_message) except Exception: pass @@ -67,3 +69,16 @@ def close(self, message_at_close=''): self.write(str(message_at_close)) self.log_file_handle.close() self.log_file_handle = None # Not having this can cause 'I/O exception on closed file' exceptions + + def truncate_message(self, message, max_size = 4 * 1024 * 1024): + """ Truncate message to a max size in bytes (4MB) at a safe point (end of line) to avoid excessively disk logging """ + if len(message) > max_size: + truncated_message = message[:max_size] + last_newline_index = truncated_message.rfind("\n") + + if last_newline_index != -1: + return truncated_message[:last_newline_index + 1] + else: + return truncated_message + + return message From 0be11fde584901005ac3f39dad3dc94f74e1b32b Mon Sep 17 00:00:00 2001 From: john feng Date: Wed, 22 Jan 2025 10:42:47 -0800 Subject: [PATCH 2/7] add unit test for filelogger --- src/core/src/local_loggers/FileLogger.py | 8 +- src/core/tests/Test_FileLogger.py | 97 ++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 src/core/tests/Test_FileLogger.py diff --git a/src/core/src/local_loggers/FileLogger.py b/src/core/src/local_loggers/FileLogger.py index 5740787ea..e8eee26c0 100644 --- a/src/core/src/local_loggers/FileLogger.py +++ b/src/core/src/local_loggers/FileLogger.py @@ -68,14 +68,14 @@ def close(self, message_at_close=''): if message_at_close is not None: self.write(str(message_at_close)) self.log_file_handle.close() - self.log_file_handle = None # Not having this can cause 'I/O exception on closed file' exceptions + self.log_file_handle = None # Reset to prevent 'I/O exception on closed file' exceptions - def truncate_message(self, message, max_size = 4 * 1024 * 1024): - """ Truncate message to a max size in bytes (4MB) at a safe point (end of line) to avoid excessively disk logging """ + def truncate_message(self, message, max_size=32 * 1024 * 1024): + """ Truncate message to a max size in bytes (32MB) at a safe point (end of line) to avoid excessively disk logging """ if len(message) > max_size: truncated_message = message[:max_size] last_newline_index = truncated_message.rfind("\n") - + if last_newline_index != -1: return truncated_message[:last_newline_index + 1] else: diff --git a/src/core/tests/Test_FileLogger.py b/src/core/tests/Test_FileLogger.py new file mode 100644 index 000000000..9ad9cf3f2 --- /dev/null +++ b/src/core/tests/Test_FileLogger.py @@ -0,0 +1,97 @@ +# Copyright 2025 Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Requires Python 2.7+ +import unittest +from core.src.local_loggers.FileLogger import FileLogger + + +class MockFileHandle: + def __init__(self, raise_on_write=False): + self.raise_on_write = raise_on_write + self.contents = "" + self.closed = False + + def write(self, message): + if self.raise_on_write: + raise Exception("Mock write exception") + self.contents += message + + def flush(self): + pass + + def close(self): + self.closed = True + + def fileno(self): + return 1 + + +class MockFileSystem: + def open(self, file_path, mode): + return MockFileHandle() + + +class MockEnvLayer: + def __init__(self): + self.file_system = MockFileSystem() + self.datetime = self + + def timestampe(self): + return "2025-01-01T00:00:00Z" + + +class TestFileLogger(unittest.TestCase): + def setUp(self): + self.mock_env_layer = MockEnvLayer() + self.log_file = "test.log" + self.file_logger = FileLogger(self.mock_env_layer, self.log_file) + + def test_write(self): + message = "Test message" + self.file_logger.write(message) + self.assertEqual(self.file_logger.log_file_handle.contents, message) + + def test_no_truncation(self): + message = "No truncation" + result = self.file_logger.truncate_message(message) + self.assertEqual(result, message) + + def test_write_truncated_message(self): + message = "A" * (32 * 1024 * 1024 - 10) + "\nExtra line.\n" # 32MB - 10 bytes to include newlines + truncate_message = self.file_logger.truncate_message(message) + self.file_logger.write(message) + self.assertTrue(truncate_message.endswith("\n")) + self.assertTrue(len(truncate_message) < (32 * 1024 * 1024 + 1)) + self.assertIn(truncate_message, self.file_logger.log_file_handle.contents) + self.assertNotIn(message, self.file_logger.log_file_handle.contents) + + def test_write_silent_failure(self): + self.file_logger.log_file_handle = MockFileHandle(raise_on_write=True) + try: + self.file_logger.write("Test message", fail_silently=True) + except Exception: + self.fail("raise an exception when fail_silently=True") + + def test_write_non_silent_failure(self): + self.file_logger.log_file_handle = MockFileHandle(raise_on_write=True) + + with self.assertRaises(Exception) as context: + self.file_logger.write("test message", fail_silently=False) + + self.assertIn("Fatal exception trying to write to log file", str(context.exception)) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 3cb7787b606ed6483f7fd3dde498490330d040c2 Mon Sep 17 00:00:00 2001 From: john feng Date: Wed, 22 Jan 2025 12:11:30 -0800 Subject: [PATCH 3/7] add unit test for flush() --- src/core/tests/Test_FileLogger.py | 32 +++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/core/tests/Test_FileLogger.py b/src/core/tests/Test_FileLogger.py index 9ad9cf3f2..2ca7f9ede 100644 --- a/src/core/tests/Test_FileLogger.py +++ b/src/core/tests/Test_FileLogger.py @@ -20,6 +20,8 @@ class MockFileHandle: def __init__(self, raise_on_write=False): self.raise_on_write = raise_on_write + self.flushed = False + self.fileno_called = False self.contents = "" self.closed = False @@ -29,14 +31,15 @@ def write(self, message): self.contents += message def flush(self): - pass + self.flushed = True + + def fileno(self): + self.fileno_called = True + return 1 # mock file def close(self): self.closed = True - def fileno(self): - return 1 - class MockFileSystem: def open(self, file_path, mode): @@ -67,6 +70,11 @@ def test_no_truncation(self): message = "No truncation" result = self.file_logger.truncate_message(message) self.assertEqual(result, message) + + def test_write_truncated_exact_size(self): + message = "A" * (32 * 1024 * 1024) + result = self.file_logger.truncate_message(message) + self.assertEqual(len(message), len(result)) def test_write_truncated_message(self): message = "A" * (32 * 1024 * 1024 - 10) + "\nExtra line.\n" # 32MB - 10 bytes to include newlines @@ -77,14 +85,7 @@ def test_write_truncated_message(self): self.assertIn(truncate_message, self.file_logger.log_file_handle.contents) self.assertNotIn(message, self.file_logger.log_file_handle.contents) - def test_write_silent_failure(self): - self.file_logger.log_file_handle = MockFileHandle(raise_on_write=True) - try: - self.file_logger.write("Test message", fail_silently=True) - except Exception: - self.fail("raise an exception when fail_silently=True") - - def test_write_non_silent_failure(self): + def test_write_false_silent_failure(self): self.file_logger.log_file_handle = MockFileHandle(raise_on_write=True) with self.assertRaises(Exception) as context: @@ -92,6 +93,13 @@ def test_write_non_silent_failure(self): self.assertIn("Fatal exception trying to write to log file", str(context.exception)) + def test_flush_success(self): + self.file_logger.flush() + + self.assertTrue(self.file_logger.log_file_handle.flushed) # verify flush() called + self.assertTrue(self.file_logger.log_file_handle.fileno_called) # verify fileno() called + + if __name__ == '__main__': unittest.main() \ No newline at end of file From 1360820b6b12010f433051a7eac70e2deb8288b2 Mon Sep 17 00:00:00 2001 From: john feng Date: Wed, 22 Jan 2025 14:24:21 -0800 Subject: [PATCH 4/7] add unit test for filelogger --- src/core/tests/Test_FileLogger.py | 76 +++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 13 deletions(-) diff --git a/src/core/tests/Test_FileLogger.py b/src/core/tests/Test_FileLogger.py index 2ca7f9ede..02a92233f 100644 --- a/src/core/tests/Test_FileLogger.py +++ b/src/core/tests/Test_FileLogger.py @@ -40,10 +40,25 @@ def fileno(self): def close(self): self.closed = True + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + class MockFileSystem: + def __init__(self): + self.files = {} + def open(self, file_path, mode): - return MockFileHandle() + if "error" in file_path: + raise Exception("Mock file open error") + + if file_path not in self.files: + self.files[file_path] = MockFileHandle() + + return self.files[file_path] class MockEnvLayer: @@ -51,7 +66,7 @@ def __init__(self): self.file_system = MockFileSystem() self.datetime = self - def timestampe(self): + def timestamp(self): return "2025-01-01T00:00:00Z" @@ -60,24 +75,37 @@ def setUp(self): self.mock_env_layer = MockEnvLayer() self.log_file = "test.log" self.file_logger = FileLogger(self.mock_env_layer, self.log_file) + + def test_init_failure(self): + """ Test when initiation object file open throws exception """ + self.mock_env_layer.file_system.open = lambda *args, **kwargs: (_ for _ in ()).throw(Exception("Mock file open error")) + with self.assertRaises(Exception) as context: + FileLogger(self.mock_env_layer, "error_log.log") + + self.assertIn("Mock file open error", str(context.exception)) def test_write(self): + """ Test FileLogger write() """ message = "Test message" self.file_logger.write(message) self.assertEqual(self.file_logger.log_file_handle.contents, message) - def test_no_truncation(self): + def test_write_message_no_truncation(self): + """ Test FileLogger truncate_message() no truncation""" message = "No truncation" result = self.file_logger.truncate_message(message) self.assertEqual(result, message) - def test_write_truncated_exact_size(self): - message = "A" * (32 * 1024 * 1024) - result = self.file_logger.truncate_message(message) - self.assertEqual(len(message), len(result)) + def test_write_message_apply_truncation(self): + """ Test FileLogger truncate_message() truncation apply """ + msg_max_size = len("A" * (32 * 1024 * 1024)) # 32 MB + message = "A" * (32 * 1024 * 1024 + 1) # 33MB + truncate_message = self.file_logger.truncate_message(message) + self.assertEqual(len(truncate_message), msg_max_size) - def test_write_truncated_message(self): - message = "A" * (32 * 1024 * 1024 - 10) + "\nExtra line.\n" # 32MB - 10 bytes to include newlines + def test_write_message_with_newline(self): + """ Test FileLogger truncate_message() truncation apply with newline """ + message = "A" * (32 * 1024 * 1024 - 10) + "\nExtra line.\n" # 32MB with newlines truncate_message = self.file_logger.truncate_message(message) self.file_logger.write(message) self.assertTrue(truncate_message.endswith("\n")) @@ -86,6 +114,7 @@ def test_write_truncated_message(self): self.assertNotIn(message, self.file_logger.log_file_handle.contents) def test_write_false_silent_failure(self): + """ Test FileLogger write(), throws exception raise_on_write is true """ self.file_logger.log_file_handle = MockFileHandle(raise_on_write=True) with self.assertRaises(Exception) as context: @@ -93,13 +122,34 @@ def test_write_false_silent_failure(self): self.assertIn("Fatal exception trying to write to log file", str(context.exception)) + def test_write_irrecoverable_exception(self): + """ Test FileLogger write_irrecoverable_exception write failure log """ + message = "test message" + self.file_logger.write_irrecoverable_exception(message) + + self.assertIn(self.file_logger.log_failure_log_file, self.mock_env_layer.file_system.files) + + failure_log = self.mock_env_layer.file_system.files[self.file_logger.log_failure_log_file] + expected_output = "\n2025-01-01T00:00:00Z> test message" + + self.assertIn(expected_output, failure_log.contents) + + def test_write_irrecoverable_exception_failure(self): + """ Test FileLogger write_irrecoverable_exception exception raised """ + self.file_logger.log_failure_log_file = "error_failure_log.log" + message = "test message" + + self.file_logger.write_irrecoverable_exception(message) + + self.assertNotIn("error_failure_log.log", self.mock_env_layer.file_system.files) + def test_flush_success(self): + """ Test FileLogger flush() and fileno() are called""" self.file_logger.flush() - self.assertTrue(self.file_logger.log_file_handle.flushed) # verify flush() called - self.assertTrue(self.file_logger.log_file_handle.fileno_called) # verify fileno() called - + self.assertTrue(self.file_logger.log_file_handle.flushed) + self.assertTrue(self.file_logger.log_file_handle.fileno_called) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() From 422301f47e0b8adf2187c22a17a3e1224e9b7093 Mon Sep 17 00:00:00 2001 From: john feng Date: Wed, 22 Jan 2025 14:32:15 -0800 Subject: [PATCH 5/7] revert comment --- src/core/src/local_loggers/FileLogger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/src/local_loggers/FileLogger.py b/src/core/src/local_loggers/FileLogger.py index e8eee26c0..330bd52a4 100644 --- a/src/core/src/local_loggers/FileLogger.py +++ b/src/core/src/local_loggers/FileLogger.py @@ -68,7 +68,7 @@ def close(self, message_at_close=''): if message_at_close is not None: self.write(str(message_at_close)) self.log_file_handle.close() - self.log_file_handle = None # Reset to prevent 'I/O exception on closed file' exceptions + self.log_file_handle = None # Not having this can cause 'I/O exception on closed file' exceptions def truncate_message(self, message, max_size=32 * 1024 * 1024): """ Truncate message to a max size in bytes (32MB) at a safe point (end of line) to avoid excessively disk logging """ From 4f7b5937bf1b7e9607d4d01e13c749e9b77eb11e Mon Sep 17 00:00:00 2001 From: john feng Date: Thu, 23 Jan 2025 15:19:35 -0800 Subject: [PATCH 6/7] modify the write truncation logic and ut --- src/core/src/local_loggers/FileLogger.py | 30 +++++++++--------- src/core/tests/Test_FileLogger.py | 40 +++++++++++++----------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/core/src/local_loggers/FileLogger.py b/src/core/src/local_loggers/FileLogger.py index 330bd52a4..e559ab441 100644 --- a/src/core/src/local_loggers/FileLogger.py +++ b/src/core/src/local_loggers/FileLogger.py @@ -25,6 +25,7 @@ def __init__(self, env_layer, log_file): self.log_file = log_file self.log_failure_log_file = log_file + ".failure" self.log_file_handle = None + self.max_msg_size = 32 * 1024 * 1024 try: self.log_file_handle = self.env_layer.file_system.open(self.log_file, "a+") except Exception as error: @@ -38,9 +39,10 @@ def __del__(self): def write(self, message, fail_silently=True): try: - truncated_message = self.truncate_message(message) + if len(message) > self.max_msg_size: + message = self.__truncate_message(message=message, max_size=self.max_msg_size) if self.log_file_handle is not None: - self.log_file_handle.write(truncated_message) + self.log_file_handle.write(message) except Exception as error: # DO NOT write any errors here to stdout failure_message = "Fatal exception trying to write to log file: " + repr(error) + ". Attempted message: " + str(message) @@ -51,10 +53,11 @@ def write(self, message, fail_silently=True): def write_irrecoverable_exception(self, message): """ A best-effort attempt to write out errors where writing to the primary log file was interrupted""" try: - truncated_message = self.truncate_message(message) + if len(message) > self.max_msg_size: + message = self.__truncate_message(message=message, max_size=self.max_msg_size) with self.env_layer.file_system.open(self.log_failure_log_file, 'a+') as fail_log: timestamp = self.env_layer.datetime.timestamp() - fail_log.write("\n" + timestamp + "> " + truncated_message) + fail_log.write("\n" + timestamp + "> " + message) except Exception: pass @@ -70,15 +73,14 @@ def close(self, message_at_close=''): self.log_file_handle.close() self.log_file_handle = None # Not having this can cause 'I/O exception on closed file' exceptions - def truncate_message(self, message, max_size=32 * 1024 * 1024): - """ Truncate message to a max size in bytes (32MB) at a safe point (end of line) to avoid excessively disk logging """ - if len(message) > max_size: - truncated_message = message[:max_size] - last_newline_index = truncated_message.rfind("\n") + def __truncate_message(self, message, max_size): + # type(str, int) -> str + """ Truncate message to a max size in bytes (32MB) at a safe point (end of the line avoid json serialization error) """ + truncated_message = message[:max_size] + last_newline_index = truncated_message.rfind("\n") - if last_newline_index != -1: - return truncated_message[:last_newline_index + 1] - else: - return truncated_message + if last_newline_index != -1: + return truncated_message[:last_newline_index + 1] + + return truncated_message - return message diff --git a/src/core/tests/Test_FileLogger.py b/src/core/tests/Test_FileLogger.py index 02a92233f..254d81096 100644 --- a/src/core/tests/Test_FileLogger.py +++ b/src/core/tests/Test_FileLogger.py @@ -85,32 +85,25 @@ def test_init_failure(self): self.assertIn("Mock file open error", str(context.exception)) def test_write(self): - """ Test FileLogger write() """ + """ Test FileLogger write() with no truncation """ message = "Test message" self.file_logger.write(message) self.assertEqual(self.file_logger.log_file_handle.contents, message) - def test_write_message_no_truncation(self): - """ Test FileLogger truncate_message() no truncation""" - message = "No truncation" - result = self.file_logger.truncate_message(message) - self.assertEqual(result, message) - - def test_write_message_apply_truncation(self): - """ Test FileLogger truncate_message() truncation apply """ - msg_max_size = len("A" * (32 * 1024 * 1024)) # 32 MB - message = "A" * (32 * 1024 * 1024 + 1) # 33MB - truncate_message = self.file_logger.truncate_message(message) - self.assertEqual(len(truncate_message), msg_max_size) - + def test_write_truncation(self): + """ Test FileLogger write() with truncation """ + max_msg_size = 32 * 1024 * 1024 + message = "A" * (32 * 1024 * 1024 + 10) + self.file_logger.write(message) + self.assertEqual(len(self.file_logger.log_file_handle.contents), max_msg_size) + def test_write_message_with_newline(self): """ Test FileLogger truncate_message() truncation apply with newline """ + max_msg_size = 32 * 1024 * 1024 message = "A" * (32 * 1024 * 1024 - 10) + "\nExtra line.\n" # 32MB with newlines - truncate_message = self.file_logger.truncate_message(message) self.file_logger.write(message) - self.assertTrue(truncate_message.endswith("\n")) - self.assertTrue(len(truncate_message) < (32 * 1024 * 1024 + 1)) - self.assertIn(truncate_message, self.file_logger.log_file_handle.contents) + self.assertTrue(self.file_logger.log_file_handle.contents.endswith("\n")) + self.assertTrue(len(self.file_logger.log_file_handle.contents), max_msg_size) self.assertNotIn(message, self.file_logger.log_file_handle.contents) def test_write_false_silent_failure(self): @@ -143,6 +136,17 @@ def test_write_irrecoverable_exception_failure(self): self.assertNotIn("error_failure_log.log", self.mock_env_layer.file_system.files) + def test_write_irrecoverable_exception_truncation(self): + """ Test FileLogger write_irrecoverable_exception write failure log with truncation """ + timestamp_size = len("\n2025-01-01T00:00:00Z> ") + max_msg_size = 32 * 1024 * 1024 + message = "A" * (32 * 1024 * 1024 + 10) + self.file_logger.write_irrecoverable_exception(message) + + self.assertIn(self.file_logger.log_failure_log_file, self.mock_env_layer.file_system.files) + failure_log = self.mock_env_layer.file_system.files[self.file_logger.log_failure_log_file] + self.assertEqual(len(failure_log.contents), max_msg_size + timestamp_size) + def test_flush_success(self): """ Test FileLogger flush() and fileno() are called""" self.file_logger.flush() From 12c6895590a65b57dd35f6514294490b9790de57 Mon Sep 17 00:00:00 2001 From: john feng Date: Fri, 31 Jan 2025 12:09:10 -0800 Subject: [PATCH 7/7] remove truncation method in filelogger --- src/core/src/local_loggers/FileLogger.py | 15 ++------------- src/core/tests/Test_FileLogger.py | 9 --------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/src/core/src/local_loggers/FileLogger.py b/src/core/src/local_loggers/FileLogger.py index e559ab441..7dba4a706 100644 --- a/src/core/src/local_loggers/FileLogger.py +++ b/src/core/src/local_loggers/FileLogger.py @@ -40,7 +40,7 @@ def __del__(self): def write(self, message, fail_silently=True): try: if len(message) > self.max_msg_size: - message = self.__truncate_message(message=message, max_size=self.max_msg_size) + message = message[:self.max_msg_size] if self.log_file_handle is not None: self.log_file_handle.write(message) except Exception as error: @@ -54,7 +54,7 @@ def write_irrecoverable_exception(self, message): """ A best-effort attempt to write out errors where writing to the primary log file was interrupted""" try: if len(message) > self.max_msg_size: - message = self.__truncate_message(message=message, max_size=self.max_msg_size) + message = message[:self.max_msg_size] with self.env_layer.file_system.open(self.log_failure_log_file, 'a+') as fail_log: timestamp = self.env_layer.datetime.timestamp() fail_log.write("\n" + timestamp + "> " + message) @@ -73,14 +73,3 @@ def close(self, message_at_close=''): self.log_file_handle.close() self.log_file_handle = None # Not having this can cause 'I/O exception on closed file' exceptions - def __truncate_message(self, message, max_size): - # type(str, int) -> str - """ Truncate message to a max size in bytes (32MB) at a safe point (end of the line avoid json serialization error) """ - truncated_message = message[:max_size] - last_newline_index = truncated_message.rfind("\n") - - if last_newline_index != -1: - return truncated_message[:last_newline_index + 1] - - return truncated_message - diff --git a/src/core/tests/Test_FileLogger.py b/src/core/tests/Test_FileLogger.py index 254d81096..2a6353863 100644 --- a/src/core/tests/Test_FileLogger.py +++ b/src/core/tests/Test_FileLogger.py @@ -97,15 +97,6 @@ def test_write_truncation(self): self.file_logger.write(message) self.assertEqual(len(self.file_logger.log_file_handle.contents), max_msg_size) - def test_write_message_with_newline(self): - """ Test FileLogger truncate_message() truncation apply with newline """ - max_msg_size = 32 * 1024 * 1024 - message = "A" * (32 * 1024 * 1024 - 10) + "\nExtra line.\n" # 32MB with newlines - self.file_logger.write(message) - self.assertTrue(self.file_logger.log_file_handle.contents.endswith("\n")) - self.assertTrue(len(self.file_logger.log_file_handle.contents), max_msg_size) - self.assertNotIn(message, self.file_logger.log_file_handle.contents) - def test_write_false_silent_failure(self): """ Test FileLogger write(), throws exception raise_on_write is true """ self.file_logger.log_file_handle = MockFileHandle(raise_on_write=True)