Skip to content

Commit 29968fe

Browse files
committed
Add elapsed time timestamps to codeplain log file
Add elapsed time timestamps (HH:MM:SS format) to file logging, matching the format shown in the TUI logs view. The timestamps reflect time since render started, accounting for pauses. - Created ElapsedTimeFormatter that uses RunState to calculate elapsed time - File logs now show [HH:MM:SS] timestamps identical to TUI display - Multi-line messages are properly indented to align with the timestamp - Console output remains unchanged with TUI-driven timestamps Fixes #76
1 parent d9c0b54 commit 29968fe

2 files changed

Lines changed: 41 additions & 5 deletions

File tree

plain2code.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from plain2code_logger import (
4141
LOGGER_NAME,
4242
CrashLogHandler,
43+
ElapsedTimeFormatter,
4344
IndentedFormatter,
4445
LoggingHandler,
4546
dump_crash_logs,
@@ -110,6 +111,7 @@ def setup_logging(
110111
root_logger.setLevel(logging.DEBUG) # Capture all logs; handlers will filter levels as needed
111112

112113
formatter = IndentedFormatter("%(levelname)s:%(name)s:%(message)s")
114+
file_formatter = ElapsedTimeFormatter(run_state)
113115

114116
if not headless:
115117
handler = LoggingHandler(event_bus, run_state)
@@ -119,7 +121,7 @@ def setup_logging(
119121
if log_to_file and log_file_path:
120122
try:
121123
file_handler = logging.FileHandler(log_file_path, mode="w", encoding="utf-8")
122-
file_handler.setFormatter(formatter)
124+
file_handler.setFormatter(file_formatter)
123125
file_handler.setLevel(configured_log_level)
124126
root_logger.addHandler(file_handler)
125127
except Exception as e:
@@ -128,7 +130,7 @@ def setup_logging(
128130
# If file logging is disabled, use CrashLogHandler to buffer logs in memory
129131
# in case we need to dump them on crash.
130132
crash_handler = CrashLogHandler()
131-
crash_handler.setFormatter(formatter)
133+
crash_handler.setFormatter(file_formatter)
132134
crash_handler.setLevel(configured_log_level)
133135
root_logger.addHandler(crash_handler)
134136

@@ -354,7 +356,7 @@ def main(): # noqa: C901
354356
)
355357
if exc_info:
356358
# Log traceback
357-
dump_crash_logs(args)
359+
dump_crash_logs(args, run_state)
358360

359361
if args.headless and (exc_info is not None or not run_state.render_succeeded):
360362
sys.exit(1)

plain2code_logger.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,40 @@ def format(self, record):
2020
return super().format(record)
2121

2222

23+
class ElapsedTimeFormatter(logging.Formatter):
24+
"""Formatter that adds elapsed time since render started, accounting for pauses."""
25+
26+
def __init__(self, run_state: RunState, fmt: str = "%(elapsed_time)s %(levelname)s %(name)s: %(message)s"):
27+
super().__init__(fmt=fmt)
28+
self.run_state = run_state
29+
30+
def format(self, record):
31+
# Calculate elapsed time the same way as LoggingHandler does for the TUI
32+
try:
33+
offset_seconds = self.run_state.render_time_accumulated + int(
34+
time.monotonic() - self.run_state.last_render_start_timestamp
35+
)
36+
except Exception:
37+
# If RunState is not available or there's any error, default to 00:00:00
38+
offset_seconds = 0
39+
40+
hours = offset_seconds // 3600
41+
minutes = (offset_seconds % 3600) // 60
42+
seconds = offset_seconds % 60
43+
elapsed_time = f"[{hours:02d}:{minutes:02d}:{seconds:02d}]"
44+
45+
# Add elapsed_time to the record so it can be used in the format string
46+
record.elapsed_time = elapsed_time
47+
48+
# Handle multi-line messages with proper indentation
49+
original_message = record.getMessage()
50+
indent = " " * len(elapsed_time + " ")
51+
modified_message = original_message.replace("\n", "\n" + indent)
52+
record.msg = modified_message
53+
54+
return super().format(record)
55+
56+
2357
class LoggingHandler(logging.Handler):
2458
def __init__(self, event_bus: EventBus, run_state: RunState):
2559
super().__init__()
@@ -85,13 +119,13 @@ def get_log_file_path(plain_file_path: Optional[str], log_file_name: str) -> Opt
85119
return os.path.join(plain_dir, log_file_name)
86120

87121

88-
def dump_crash_logs(args, formatter=None):
122+
def dump_crash_logs(args, run_state: RunState, formatter=None):
89123
"""Dump buffered logs to file if CrashLogHandler is present."""
90124
if args.log_to_file:
91125
return
92126

93127
if formatter is None:
94-
formatter = IndentedFormatter("%(levelname)s:%(name)s:%(message)s")
128+
formatter = ElapsedTimeFormatter(run_state)
95129

96130
root_logger = logging.getLogger(LOGGER_NAME)
97131
crash_handler = next((h for h in root_logger.handlers if isinstance(h, CrashLogHandler)), None)

0 commit comments

Comments
 (0)