remote-claude 0.2.11 → 0.2.13

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.
package/server/server.py CHANGED
@@ -42,6 +42,15 @@ from utils.session import (
42
42
 
43
43
  logger = logging.getLogger('Server')
44
44
 
45
+ # Server 日志级别配置
46
+ _SERVER_LOG_LEVEL = os.getenv("SERVER_LOG_LEVEL", "INFO").upper()
47
+ SERVER_LOG_LEVEL_MAP = {
48
+ "DEBUG": logging.DEBUG,
49
+ "INFO": logging.INFO,
50
+ "WARNING": logging.WARNING,
51
+ "ERROR": logging.ERROR,
52
+ }.get(_SERVER_LOG_LEVEL, logging.INFO) # 默认 INFO
53
+
45
54
  # 加载用户 .env 配置(支持 CLAUDE_COMMAND 等)
46
55
  try:
47
56
  from dotenv import load_dotenv
@@ -126,11 +135,11 @@ class OutputWatcher:
126
135
  # 持久化解析器(跨帧保留 dot_row_cache);由调用方注入(可插拔架构)
127
136
  import logging as _logging
128
137
  _logging.getLogger('ComponentParser').setLevel(_logging.DEBUG)
129
- _blink_handler = _logging.FileHandler(
130
- f"/tmp/remote-claude/{safe_name}_blink.log"
131
- )
132
- _blink_handler.setFormatter(_logging.Formatter('%(asctime)s %(message)s', '%H:%M:%S'))
133
- _logging.getLogger('ComponentParser').addHandler(_blink_handler)
138
+ # 创建专用 logger 替换直接文件写入
139
+ self._blink_logger = _logging.getLogger(f'OutputWatcher.{self._session_name}.blink')
140
+ self._blink_logger.setLevel(_logging.DEBUG)
141
+ self._flush_logger = _logging.getLogger(f'OutputWatcher.{self._session_name}.flush')
142
+ self._flush_logger.setLevel(_logging.DEBUG)
134
143
  if parser is None:
135
144
  from parsers import ClaudeParser
136
145
  parser = ClaudeParser()
@@ -258,11 +267,7 @@ class OutputWatcher:
258
267
  pass
259
268
  break
260
269
  if last_ob_blink:
261
- _blink_log = open(f"/tmp/remote-claude/{self._session_name}_blink.log", "a")
262
- _blink_log.write(
263
- f"[{time.strftime('%H:%M:%S')}] raw-blink last_ob={last_ob_content!r}\n"
264
- )
265
- _blink_log.close()
270
+ self._blink_logger.debug(f"raw-blink last_ob={last_ob_content!r}")
266
271
 
267
272
  self._frame_window.append(_FrameObs(
268
273
  ts=now,
@@ -308,23 +313,19 @@ class OutputWatcher:
308
313
  if len(chars) > 1 or len(fgs) > 1 or len(bolds) > 1:
309
314
  window_block_active = True
310
315
  # 记录字符变化触发原因
311
- _blink_log = open(f"/tmp/remote-claude/{self._session_name}_blink.log", "a")
312
- _blink_log.write(
313
- f"[{time.strftime('%H:%M:%S')}] char-change row={last_ob_start_row}"
314
- f" chars={chars} fgs={fgs} bolds={bolds}\n"
316
+ self._blink_logger.debug(
317
+ f"char-change row={last_ob_start_row}"
318
+ f" chars={chars} fgs={fgs} bolds={bolds}"
315
319
  )
316
- _blink_log.close()
317
320
 
318
321
  if window_block_active:
319
322
  for b in reversed(visible_blocks):
320
323
  if isinstance(b, OutputBlock):
321
- _blink_log = open(f"/tmp/remote-claude/{self._session_name}_blink.log", "a")
322
- _blink_log.write(
323
- f"[{time.strftime('%H:%M:%S')}] win-smooth last_ob={b.content[:40]!r}"
324
+ self._blink_logger.debug(
325
+ f"win-smooth last_ob={b.content[:40]!r}"
324
326
  f" window_frames={len(window_list)}"
325
- f" blink_frames={sum(1 for o in window_list if o.block_blink)}\n"
327
+ f" blink_frames={sum(1 for o in window_list if o.block_blink)}"
326
328
  )
327
- _blink_log.close()
328
329
  b.is_streaming = True
329
330
  break
330
331
 
@@ -373,13 +374,12 @@ class OutputWatcher:
373
374
  self._on_snapshot(window)
374
375
  t4 = time.time()
375
376
 
376
- with open(f"/tmp/remote-claude/{_safe_filename(self._session_name)}_flush.log", "a") as _f:
377
- _f.write(
378
- f"[flush] screen_log={1000*(t1-t0):.1f}ms parse={1000*(t2-t1):.1f}ms "
379
- f"msg_log={1000*(t3-t2):.1f}ms snapshot={1000*(t4-t3):.1f}ms "
380
- f"total={1000*(t4-t0):.1f}ms rows={self._rows}\n"
381
- f" └─ {self._parser.last_parse_timing}\n"
382
- )
377
+ self._flush_logger.debug(
378
+ f"[flush] screen_log={1000*(t1-t0):.1f}ms parse={1000*(t2-t1):.1f}ms "
379
+ f"msg_log={1000*(t3-t2):.1f}ms snapshot={1000*(t4-t3):.1f}ms "
380
+ f"total={1000*(t4-t0):.1f}ms rows={self._rows}\n"
381
+ f" └─ {self._parser.last_parse_timing}"
382
+ )
383
383
 
384
384
  except Exception as e:
385
385
  print(f"[OutputWatcher] flush 失败: {e}")
@@ -727,6 +727,14 @@ class ProxyServer:
727
727
  not hasattr(handler, '_debug_handler'):
728
728
  root_logger.removeHandler(handler)
729
729
 
730
+ # 重定向 sys.stderr 到 ~/.remote-claude/server.error.log
731
+ # 注意:这不会影响外层的 2>> startup.log,但 Python 的 stderr 输出会走这里
732
+ # 适用于:print(..., file=sys.stderr)、logging 的 StreamHandler 等
733
+ # 不适用于:C 扩展模块直接写文件描述符 2、解释器崩溃等底层错误
734
+ error_log_path = os.path.expanduser('~/.remote-claude/server.error.log')
735
+ sys.stderr = open(error_log_path, 'w', encoding='utf-8')
736
+ logger.info(f"已重定向 stderr 到 {error_log_path}")
737
+
730
738
  # 添加运行阶段日志文件
731
739
  safe_name = _safe_filename(self.session_name)
732
740
  runtime_handler = logging.FileHandler(
@@ -740,6 +748,21 @@ class ProxyServer:
740
748
  runtime_handler._runtime_handler = True # 标记,方便后续清理
741
749
  root_logger.addHandler(runtime_handler)
742
750
 
751
+ # DEBUG 级别时额外记录调试日志到独立文件
752
+ if SERVER_LOG_LEVEL_MAP == logging.DEBUG:
753
+ debug_handler = logging.FileHandler(
754
+ f"{SOCKET_DIR}/{safe_name}_debug.log",
755
+ encoding="utf-8"
756
+ )
757
+ debug_handler.setLevel(logging.DEBUG)
758
+ debug_handler.setFormatter(logging.Formatter(
759
+ "%(asctime)s.%(msecs)03d [%(name)s] %(levelname)s %(message)s",
760
+ datefmt="%Y-%m-%d %H:%M:%S"
761
+ ))
762
+ debug_handler._debug_handler = True # 标记,方便后续清理
763
+ root_logger.addHandler(debug_handler)
764
+ logger.info(f"已启用 DEBUG 日志: {safe_name}_debug.log")
765
+
743
766
  logger.info(f"日志已切换到运行阶段: {safe_name}_server.log")
744
767
 
745
768
  def _get_effective_cmd(self) -> str:
package/utils/session.py CHANGED
@@ -99,7 +99,15 @@ def tmux_create_session(session_name: str, command: str, detached: bool = True)
99
99
  if detached:
100
100
  args.append("-d")
101
101
  args.extend(["-x", "200", "-y", "50"]) # 默认大小
102
- args.append(command)
102
+
103
+ # 将 stderr 重定向到 startup.log,捕获 Python 启动错误(如 ModuleNotFoundError)
104
+ # startup.log 位于 ~/.remote-claude/startup.log
105
+ startup_log = USER_DATA_DIR / "startup.log"
106
+ startup_log.parent.mkdir(parents=True, exist_ok=True)
107
+ # 直接在命令末尾添加重定向,使用 str(startup_log) 确保路径正确展开
108
+ command_with_stderr = f"{command} 2>> {startup_log}"
109
+
110
+ args.append(command_with_stderr)
103
111
 
104
112
  import logging as _logging
105
113
  _logging.getLogger('Start').info(f"tmux_cmd: {' '.join(args)}")