echoss-common 2.0.0__tar.gz → 2.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {echoss_common-2.0.0/echoss_common.egg-info → echoss_common-2.0.1}/PKG-INFO +1 -1
  2. {echoss_common-2.0.0 → echoss_common-2.0.1}/echoss_common/echoss_logger.py +23 -0
  3. {echoss_common-2.0.0 → echoss_common-2.0.1/echoss_common.egg-info}/PKG-INFO +1 -1
  4. {echoss_common-2.0.0 → echoss_common-2.0.1}/pyproject.toml +1 -1
  5. {echoss_common-2.0.0 → echoss_common-2.0.1}/tests/test_echoss_logger_2x.py +51 -0
  6. {echoss_common-2.0.0 → echoss_common-2.0.1}/LICENSE +0 -0
  7. {echoss_common-2.0.0 → echoss_common-2.0.1}/MANIFEST.in +0 -0
  8. {echoss_common-2.0.0 → echoss_common-2.0.1}/README.md +0 -0
  9. {echoss_common-2.0.0 → echoss_common-2.0.1}/echoss_common/__init__.py +0 -0
  10. {echoss_common-2.0.0 → echoss_common-2.0.1}/echoss_common/config_fileformat.py +0 -0
  11. {echoss_common-2.0.0 → echoss_common-2.0.1}/echoss_common.egg-info/SOURCES.txt +0 -0
  12. {echoss_common-2.0.0 → echoss_common-2.0.1}/echoss_common.egg-info/dependency_links.txt +0 -0
  13. {echoss_common-2.0.0 → echoss_common-2.0.1}/echoss_common.egg-info/requires.txt +0 -0
  14. {echoss_common-2.0.0 → echoss_common-2.0.1}/echoss_common.egg-info/top_level.txt +0 -0
  15. {echoss_common-2.0.0 → echoss_common-2.0.1}/setup.cfg +0 -0
  16. {echoss_common-2.0.0 → echoss_common-2.0.1}/tests/scripts/check_xml_samples.py +0 -0
  17. {echoss_common-2.0.0 → echoss_common-2.0.1}/tests/scripts/hold_file.py +0 -0
  18. {echoss_common-2.0.0 → echoss_common-2.0.1}/tests/scripts/spike_default_log_file.py +0 -0
  19. {echoss_common-2.0.0 → echoss_common-2.0.1}/tests/scripts/verify_xml_edge_cases.py +0 -0
  20. {echoss_common-2.0.0 → echoss_common-2.0.1}/tests/test_config_fileformat.py +0 -0
  21. {echoss_common-2.0.0 → echoss_common-2.0.1}/tests/test_config_fileformat_2x.py +0 -0
  22. {echoss_common-2.0.0 → echoss_common-2.0.1}/tests/test_echoss_logger_1.py +0 -0
  23. {echoss_common-2.0.0 → echoss_common-2.0.1}/tests/test_echoss_logger_2.py +0 -0
  24. {echoss_common-2.0.0 → echoss_common-2.0.1}/tests/test_echoss_logger_json.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echoss-common
3
- Version: 2.0.0
3
+ Version: 2.0.1
4
4
  Summary: echoss AI Bigdata Center 공통 유틸리티 라이브러리 (Logger, JsonLogDict, config_fileformat)
5
5
  License-Expression: Apache-2.0
6
6
  Classifier: Programming Language :: Python :: 3
@@ -52,6 +52,23 @@ class JsonLineFormatter(logging.Formatter):
52
52
  return json.dumps(payload, ensure_ascii=False, default=str)
53
53
 
54
54
 
55
+ # Windows cannot rename a file another handle keeps open, so a replaced stream only arises
56
+ # there through copy-truncate, which keeps the inode. Keep the 2.0.0 behaviour on Windows.
57
+ _STALE_STREAM_CHECK = os.name != "nt"
58
+
59
+
60
+ # True when another handler already rotated the file this handler is still writing to.
61
+ def _stream_replaced(handler) -> bool:
62
+ if not _STALE_STREAM_CHECK or handler.stream is None:
63
+ return False
64
+ try:
65
+ mine = os.fstat(handler.stream.fileno())
66
+ disk = os.stat(handler.baseFilename)
67
+ except OSError:
68
+ return False
69
+ return (mine.st_dev, mine.st_ino) != (disk.st_dev, disk.st_ino)
70
+
71
+
55
72
  # On Windows, renaming a log file that another handle keeps open fails with PermissionError (WinError 32).
56
73
  # Fall back to copy-then-truncate so no record is lost, and always move rolloverAt forward.
57
74
  class CopyTruncateRotatingHandler(TimedRotatingFileHandler):
@@ -73,6 +90,12 @@ class CopyTruncateRotatingHandler(TimedRotatingFileHandler):
73
90
  f.truncate(0)
74
91
 
75
92
  def doRollover(self):
93
+ if _stream_replaced(self):
94
+ self.stream.close()
95
+ self.stream = None # a failed _open() must leave None so the next emit reopens, as the stdlib does
96
+ self.stream = self._open()
97
+ self.rolloverAt = self.computeRollover(int(time.time()))
98
+ return
76
99
  super().doRollover()
77
100
  now = int(time.time())
78
101
  if self.rolloverAt <= now:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: echoss-common
3
- Version: 2.0.0
3
+ Version: 2.0.1
4
4
  Summary: echoss AI Bigdata Center 공통 유틸리티 라이브러리 (Logger, JsonLogDict, config_fileformat)
5
5
  License-Expression: Apache-2.0
6
6
  Classifier: Programming Language :: Python :: 3
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
  # 패키지 메타데이터
7
7
  [project]
8
8
  name="echoss-common"
9
- version="2.0.0"
9
+ version="2.0.1"
10
10
  description="echoss AI Bigdata Center 공통 유틸리티 라이브러리 (Logger, JsonLogDict, config_fileformat)"
11
11
  readme = "README.md"
12
12
  license = "Apache-2.0"
@@ -164,6 +164,57 @@ def test_existing_dated_file_is_kept_and_rollover_moves_forward(tmp_path):
164
164
  assert handler.rolloverAt > now
165
165
 
166
166
 
167
+ # Two loggers, one handler each, one file, rotating every second: writes A1 B1 | A2 B2 | A3 B3
168
+ def _write_through_two_handlers(tmp_path):
169
+ log_file = tmp_path / "e.log"
170
+ loggers = []
171
+ for name in ("two_a", "two_b"):
172
+ handler = echoss_logger.CopyTruncateRotatingHandler(str(log_file), when="S", interval=1, encoding="utf-8")
173
+ handler.setFormatter(logging.Formatter("%(message)s"))
174
+ logger = logging.getLogger(name)
175
+ logger.propagate = False
176
+ logger.setLevel(logging.INFO)
177
+ logger.addHandler(handler)
178
+ loggers.append(logger)
179
+ try:
180
+ for i in (1, 2, 3):
181
+ loggers[0].info("A%d", i)
182
+ loggers[1].info("B%d", i)
183
+ if i < 3:
184
+ time.sleep(1.2)
185
+ finally:
186
+ for logger in loggers:
187
+ for handler in list(logger.handlers):
188
+ logger.removeHandler(handler)
189
+ handler.close()
190
+ rotated = sorted(tmp_path.glob("e.log.*"))
191
+ return log_file.read_text(encoding="utf-8").split(), [p.read_text(encoding="utf-8").split() for p in rotated]
192
+
193
+
194
+ # T-M1
195
+ def test_two_handlers_on_one_file_write_current_file_after_rotation(tmp_path):
196
+ current, rotated = _write_through_two_handlers(tmp_path)
197
+
198
+ assert current == ["A3", "B3"]
199
+ assert rotated == [["A1", "B1"], ["A2", "B2"]]
200
+
201
+
202
+ # T-M2
203
+ def test_two_handlers_lose_and_duplicate_no_record(tmp_path):
204
+ current, rotated = _write_through_two_handlers(tmp_path)
205
+
206
+ assert sorted(current + [line for lines in rotated for line in lines]) == ["A1", "A2", "A3", "B1", "B2", "B3"]
207
+
208
+
209
+ # T-M3
210
+ def test_stale_stream_check_off_keeps_2_0_0_layout(tmp_path, monkeypatch):
211
+ monkeypatch.setattr(echoss_logger, "_STALE_STREAM_CHECK", False, raising=False)
212
+ current, rotated = _write_through_two_handlers(tmp_path)
213
+
214
+ assert current == ["A3"]
215
+ assert rotated == [["A1", "B1", "B2", "B3"], ["A2"]]
216
+
217
+
167
218
  # T-08
168
219
  @pytest.mark.parametrize("func, params", [
169
220
  (echoss_common.get_app_logger,
File without changes
File without changes
File without changes
File without changes