Skip to content

Commit bbf558c

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 1d4289e commit bbf558c

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
@@ -41,6 +41,7 @@
4141
from plain2code_logger import (
4242
LOGGER_NAME,
4343
CrashLogHandler,
44+
ElapsedTimeFormatter,
4445
IndentedFormatter,
4546
LoggingHandler,
4647
dump_crash_logs,
@@ -90,6 +91,7 @@ def setup_logging(
9091
root_logger.setLevel(logging.DEBUG) # Capture all logs; handlers will filter levels as needed
9192

9293
formatter = IndentedFormatter("%(levelname)s:%(name)s:%(message)s")
94+
file_formatter = ElapsedTimeFormatter(run_state)
9395

9496
if not headless:
9597
handler = LoggingHandler(event_bus, run_state)
@@ -99,7 +101,7 @@ def setup_logging(
99101
if log_to_file and log_file_path:
100102
try:
101103
file_handler = logging.FileHandler(log_file_path, mode="w", encoding="utf-8")
102-
file_handler.setFormatter(formatter)
104+
file_handler.setFormatter(file_formatter)
103105
file_handler.setLevel(configured_log_level)
104106
root_logger.addHandler(file_handler)
105107
except Exception as e:
@@ -108,7 +110,7 @@ def setup_logging(
108110
# If file logging is disabled, use CrashLogHandler to buffer logs in memory
109111
# in case we need to dump them on crash.
110112
crash_handler = CrashLogHandler()
111-
crash_handler.setFormatter(formatter)
113+
crash_handler.setFormatter(file_formatter)
112114
crash_handler.setLevel(configured_log_level)
113115
root_logger.addHandler(crash_handler)
114116

@@ -355,7 +357,7 @@ def main(): # noqa: C901
355357
)
356358
if exc_info:
357359
# Log traceback
358-
dump_crash_logs(args)
360+
dump_crash_logs(args, run_state)
359361

360362
if args.headless and (exc_info is not None or not run_state.render_succeeded):
361363
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)