(log_path: str)
| 265 | |
| 266 | |
| 267 | def _export_logs(log_path: str): |
| 268 | action = "export_logs" |
| 269 | op_id = new_op_id() |
| 270 | token = set_op_id(op_id) |
| 271 | started = time.perf_counter() |
| 272 | log_event(logging.INFO, "ui_action_started", "用户点击导出日志", action=action, status="started") |
| 273 | save_path = filedialog.asksaveasfilename(defaultextension=".log", filetypes=[("日志文件", "*.log")]) |
| 274 | if not save_path: |
| 275 | log_event(logging.INFO, "ui_action_finished", "用户取消导出日志", action=action, status="cancel") |
| 276 | reset_op_id(token) |
| 277 | return |
| 278 | try: |
| 279 | res = export_logs(log_path, save_path) |
| 280 | duration_ms = int((time.perf_counter() - started) * 1000) |
| 281 | if res.ok and res.data: |
| 282 | log_event(logging.INFO, "ui_action_finished", f"日志已导出到:{save_path}", action=action, status="ok", duration_ms=duration_ms) |
| 283 | messagebox.showinfo("成功", f"日志已导出到:{save_path}") |
| 284 | else: |
| 285 | msg = res.error.get("message") if res.error else "导出失败" |
| 286 | log_event( |
| 287 | logging.ERROR, |
| 288 | "ui_action_finished", |
| 289 | msg, |
| 290 | action=action, |
| 291 | status="failed", |
| 292 | duration_ms=duration_ms, |
| 293 | error_type=(res.error or {}).get("code", "EXPORT_LOGS_FAILED"), |
| 294 | error_message=msg, |
| 295 | ) |
| 296 | messagebox.showerror("错误", msg) |
| 297 | except Exception as e: |
| 298 | duration_ms = int((time.perf_counter() - started) * 1000) |
| 299 | log_event( |
| 300 | logging.ERROR, |
| 301 | "ui_action_exception", |
| 302 | f"导出日志异常: {e}", |
| 303 | action=action, |
| 304 | status="failed", |
| 305 | duration_ms=duration_ms, |
| 306 | error_type=type(e).__name__, |
| 307 | error_message=str(e), |
| 308 | traceback=traceback.format_exc(), |
| 309 | exc_info=True, |
| 310 | ) |
| 311 | messagebox.showerror("错误", str(e)) |
| 312 | finally: |
| 313 | reset_op_id(token) |
no test coverage detected