新增日志
This commit is contained in:
parent
a986d57492
commit
1947f5dcda
@ -2,6 +2,9 @@ import psycopg2
|
|||||||
from psycopg2.extras import RealDictCursor
|
from psycopg2.extras import RealDictCursor
|
||||||
import datetime
|
import datetime
|
||||||
import uuid
|
import uuid
|
||||||
|
from utils.logger import get_logger, log_timing
|
||||||
|
|
||||||
|
logger = get_logger("db_manager")
|
||||||
|
|
||||||
class DatabaseManager:
|
class DatabaseManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -14,14 +17,16 @@ class DatabaseManager:
|
|||||||
}
|
}
|
||||||
self.schema = '"tzwork"'
|
self.schema = '"tzwork"'
|
||||||
|
|
||||||
|
@log_timing
|
||||||
def get_connection(self):
|
def get_connection(self):
|
||||||
try:
|
try:
|
||||||
conn = psycopg2.connect(**self.config)
|
conn = psycopg2.connect(**self.config)
|
||||||
return conn
|
return conn
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Database connection error: {e}")
|
logger.error(f"Database connection error: {e}", exc_info=True)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@log_timing
|
||||||
def insert_analysis_data(self, data_list):
|
def insert_analysis_data(self, data_list):
|
||||||
conn = self.get_connection()
|
conn = self.get_connection()
|
||||||
if not conn:
|
if not conn:
|
||||||
@ -60,6 +65,7 @@ class DatabaseManager:
|
|||||||
conn.close()
|
conn.close()
|
||||||
return False, str(e)
|
return False, str(e)
|
||||||
|
|
||||||
|
@log_timing
|
||||||
def authenticate_user(self, user_id, password_md5):
|
def authenticate_user(self, user_id, password_md5):
|
||||||
conn = self.get_connection()
|
conn = self.get_connection()
|
||||||
if not conn:
|
if not conn:
|
||||||
@ -93,11 +99,12 @@ class DatabaseManager:
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Authentication error: {e}")
|
logger.error(f"Authentication error: {e}", exc_info=True)
|
||||||
if conn:
|
if conn:
|
||||||
conn.close()
|
conn.close()
|
||||||
return False, f"认证失败: {str(e)}"
|
return False, f"认证失败: {str(e)}"
|
||||||
|
|
||||||
|
@log_timing
|
||||||
def get_latest_sample_data(self):
|
def get_latest_sample_data(self):
|
||||||
"""
|
"""
|
||||||
Retrieves the most recently measured sample data, flattened.
|
Retrieves the most recently measured sample data, flattened.
|
||||||
@ -145,7 +152,7 @@ class DatabaseManager:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Query error: {e}")
|
logger.error(f"Query error: {e}", exc_info=True)
|
||||||
if conn:
|
if conn:
|
||||||
conn.close()
|
conn.close()
|
||||||
return None
|
return None
|
||||||
@ -159,6 +166,7 @@ class DatabaseManager:
|
|||||||
return "合格"
|
return "合格"
|
||||||
return "不合格"
|
return "不合格"
|
||||||
|
|
||||||
|
@log_timing
|
||||||
def insert_inspection_data(self, inspection_data):
|
def insert_inspection_data(self, inspection_data):
|
||||||
"""
|
"""
|
||||||
插入检验数据到 e_pz_hjqy_import 表
|
插入检验数据到 e_pz_hjqy_import 表
|
||||||
@ -281,6 +289,7 @@ class DatabaseManager:
|
|||||||
conn.close()
|
conn.close()
|
||||||
return False, f"插入失败: {str(e)}"
|
return False, f"插入失败: {str(e)}"
|
||||||
|
|
||||||
|
@log_timing
|
||||||
def query_by_gch(self, gch):
|
def query_by_gch(self, gch):
|
||||||
"""
|
"""
|
||||||
根据工程号查询产地、规格、炉号、材质等信息
|
根据工程号查询产地、规格、炉号、材质等信息
|
||||||
@ -322,6 +331,7 @@ class DatabaseManager:
|
|||||||
conn.close()
|
conn.close()
|
||||||
return None, f"查询失败: {str(e)}"
|
return None, f"查询失败: {str(e)}"
|
||||||
|
|
||||||
|
@log_timing
|
||||||
def get_inspection_list(self, limit=10, offset=0, user_id=''):
|
def get_inspection_list(self, limit=10, offset=0, user_id=''):
|
||||||
"""
|
"""
|
||||||
分页获取检验数据列表
|
分页获取检验数据列表
|
||||||
@ -383,6 +393,7 @@ class DatabaseManager:
|
|||||||
conn.close()
|
conn.close()
|
||||||
return None, f"查询失败: {str(e)}"
|
return None, f"查询失败: {str(e)}"
|
||||||
|
|
||||||
|
@log_timing
|
||||||
def query_standard_range(self, heat_number, heat_number2):
|
def query_standard_range(self, heat_number, heat_number2):
|
||||||
"""
|
"""
|
||||||
根据炉号1(tph)和炉号2(company_ph)查询标准值范围
|
根据炉号1(tph)和炉号2(company_ph)查询标准值范围
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from ui.inspection_window import InspectionWindow
|
|||||||
from ui.login_window import LoginWindow
|
from ui.login_window import LoginWindow
|
||||||
from src.session_manager import SessionManager
|
from src.session_manager import SessionManager
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from utils.logger import setup_logging, get_logger
|
||||||
|
|
||||||
def get_mock_data():
|
def get_mock_data():
|
||||||
return [
|
return [
|
||||||
@ -73,6 +74,9 @@ def get_mock_data():
|
|||||||
]
|
]
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
setup_logging()
|
||||||
|
logger = get_logger("inspection_app")
|
||||||
|
logger.info("Application starting...")
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
font = app.font()
|
font = app.font()
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
import os
|
import os
|
||||||
|
from utils.logger import get_logger, log_timing
|
||||||
|
logger = get_logger("xml_parser")
|
||||||
|
|
||||||
class XmlParser:
|
class XmlParser:
|
||||||
|
@log_timing
|
||||||
def parse_file(self, file_path):
|
def parse_file(self, file_path):
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
return None, f"文件未找到: {file_path}"
|
return None, f"文件未找到: {file_path}"
|
||||||
@ -123,6 +126,5 @@ class XmlParser:
|
|||||||
return extracted_data, "Success"
|
return extracted_data, "Success"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
logger.error(f"Parse error: {str(e)}", exc_info=True)
|
||||||
traceback.print_exc()
|
|
||||||
return None, f"Parse error: {str(e)}"
|
return None, f"Parse error: {str(e)}"
|
||||||
|
|||||||
@ -8,6 +8,8 @@ from src.db_manager import DatabaseManager
|
|||||||
from src.xml_parser import XmlParser
|
from src.xml_parser import XmlParser
|
||||||
from utils.readMTP import MTPHandler
|
from utils.readMTP import MTPHandler
|
||||||
from src.session_manager import SessionManager
|
from src.session_manager import SessionManager
|
||||||
|
from utils.logger import get_logger, log_timing_context
|
||||||
|
logger = get_logger("base_page")
|
||||||
|
|
||||||
class BaseInspectionPage(QDialog):
|
class BaseInspectionPage(QDialog):
|
||||||
def __init__(self, title, parent=None):
|
def __init__(self, title, parent=None):
|
||||||
@ -172,6 +174,7 @@ class BaseInspectionPage(QDialog):
|
|||||||
self.main_layout.addWidget(button_bar)
|
self.main_layout.addWidget(button_bar)
|
||||||
|
|
||||||
def on_sync(self):
|
def on_sync(self):
|
||||||
|
logger.info(f"[{self.page_type}] 开始同步操作")
|
||||||
sync_mode = self.settings.value("sync_mode", "local")
|
sync_mode = self.settings.value("sync_mode", "local")
|
||||||
|
|
||||||
xml_path = None
|
xml_path = None
|
||||||
@ -189,13 +192,15 @@ class BaseInspectionPage(QDialog):
|
|||||||
latest_xml = max(xml_files, key=os.path.getmtime)
|
latest_xml = max(xml_files, key=os.path.getmtime)
|
||||||
xml_path = latest_xml
|
xml_path = latest_xml
|
||||||
target_xml_name = os.path.basename(latest_xml)
|
target_xml_name = os.path.basename(latest_xml)
|
||||||
|
logger.info(f"本地模式,选定文件: {target_xml_name}")
|
||||||
else:
|
else:
|
||||||
device_name = self.settings.value("mtp_device", "Trizeps VII")
|
device_name = self.settings.value("mtp_device", "Trizeps VII")
|
||||||
remote_path = self.settings.value("mtp_path", "")
|
remote_path = self.settings.value("mtp_path", "")
|
||||||
|
|
||||||
self.show_info("正在同步", f"正在从设备 [{device_name}] 读取最新数据...")
|
self.show_info("正在同步", f"正在从设备 [{device_name}] 读取最新数据...")
|
||||||
|
|
||||||
local_cache, filename = self.mtp_handler.get_latest_xml_windows(device_name, remote_path)
|
with log_timing_context(f"MTP同步 设备={device_name}", "base_page"):
|
||||||
|
local_cache, filename = self.mtp_handler.get_latest_xml_windows(device_name, remote_path)
|
||||||
if not local_cache:
|
if not local_cache:
|
||||||
self.show_info("同步失败", f"MTP同步错误: {filename}")
|
self.show_info("同步失败", f"MTP同步错误: {filename}")
|
||||||
return
|
return
|
||||||
@ -205,7 +210,8 @@ class BaseInspectionPage(QDialog):
|
|||||||
|
|
||||||
# 1.解析xml
|
# 1.解析xml
|
||||||
parser = XmlParser()
|
parser = XmlParser()
|
||||||
parsed_data, msg = parser.parse_file(xml_path)
|
with log_timing_context("XML解析", "base_page"):
|
||||||
|
parsed_data, msg = parser.parse_file(xml_path)
|
||||||
|
|
||||||
if not parsed_data:
|
if not parsed_data:
|
||||||
self.show_info("同步失败", f"XML解析错误: {msg}")
|
self.show_info("同步失败", f"XML解析错误: {msg}")
|
||||||
@ -213,19 +219,21 @@ class BaseInspectionPage(QDialog):
|
|||||||
|
|
||||||
# 2. 写入数据
|
# 2. 写入数据
|
||||||
db = DatabaseManager()
|
db = DatabaseManager()
|
||||||
success, db_msg = db.insert_analysis_data(parsed_data)
|
with log_timing_context("DB写入分析数据", "base_page"):
|
||||||
|
success, db_msg = db.insert_analysis_data(parsed_data)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
self.show_info("数据库错误", f"插入失败: {db_msg}\n(请检查数据库连接)")
|
self.show_info("数据库错误", f"插入失败: {db_msg}\n(请检查数据库连接)")
|
||||||
return
|
return
|
||||||
|
|
||||||
latest_data = db.get_latest_sample_data()
|
with log_timing_context("DB读取最新样本数据", "base_page"):
|
||||||
|
latest_data = db.get_latest_sample_data()
|
||||||
if latest_data:
|
if latest_data:
|
||||||
self.update_ui_with_data(latest_data)
|
self.update_ui_with_data(latest_data)
|
||||||
self.show_info("同步成功", f"已同步 {len(parsed_data)} 条元素数据。\n源文件: {target_xml_name}")
|
self.show_info("同步成功", f"已同步 {len(parsed_data)} 条元素数据。\n源文件: {target_xml_name}")
|
||||||
else:
|
else:
|
||||||
self.show_info("同步异常", "数据插入成功但无法读取回显。")
|
self.show_info("同步异常", "数据插入成功但无法读取回显。")
|
||||||
|
logger.info(f"[{self.page_type}] 同步操作完成")
|
||||||
def update_ui_with_data(self, data):
|
def update_ui_with_data(self, data):
|
||||||
### 更新UI界面,如果不符合,对不符合的元素字体标红,并且
|
### 更新UI界面,如果不符合,对不符合的元素字体标红,并且
|
||||||
pass
|
pass
|
||||||
@ -236,6 +244,7 @@ class BaseInspectionPage(QDialog):
|
|||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
"""提交检验数据到 e_pz_hjqy_import 表"""
|
"""提交检验数据到 e_pz_hjqy_import 表"""
|
||||||
|
logger.info(f"[{self.page_type}] 开始提交检验数据")
|
||||||
try:
|
try:
|
||||||
# 1. 收集表单数据
|
# 1. 收集表单数据
|
||||||
gch = self.inputs.get("batch_no").text().strip() if self.inputs.get("batch_no") else ""
|
gch = self.inputs.get("batch_no").text().strip() if self.inputs.get("batch_no") else ""
|
||||||
@ -296,15 +305,19 @@ class BaseInspectionPage(QDialog):
|
|||||||
|
|
||||||
# 6. 插入数据库
|
# 6. 插入数据库
|
||||||
db = DatabaseManager()
|
db = DatabaseManager()
|
||||||
success, message = db.insert_inspection_data(inspection_data)
|
with log_timing_context(f"DB提交检验数据 gch={gch}", "base_page"):
|
||||||
|
success, message = db.insert_inspection_data(inspection_data)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
|
logger.info(f"[{self.page_type}] 提交成功: {message}")
|
||||||
self.show_info("提交成功", message)
|
self.show_info("提交成功", message)
|
||||||
self.clear_data()
|
self.clear_data()
|
||||||
else:
|
else:
|
||||||
|
logger.warning(f"[{self.page_type}] 提交失败: {message}")
|
||||||
self.show_info("提交失败", message)
|
self.show_info("提交失败", message)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"[{self.page_type}] 提交异常: {e}", exc_info=True)
|
||||||
self.show_info("提交异常", f"发生错误: {str(e)}")
|
self.show_info("提交异常", f"发生错误: {str(e)}")
|
||||||
|
|
||||||
def show_info(self, title, message):
|
def show_info(self, title, message):
|
||||||
|
|||||||
@ -6,6 +6,8 @@ from openpyxl.styles.builtins import headline_1
|
|||||||
|
|
||||||
from ui.base_page import BaseInspectionPage
|
from ui.base_page import BaseInspectionPage
|
||||||
from src.db_manager import DatabaseManager
|
from src.db_manager import DatabaseManager
|
||||||
|
from utils.logger import get_logger, log_timing_context
|
||||||
|
logger = get_logger("incoming_inspection")
|
||||||
|
|
||||||
class IncomingInspectionPage(BaseInspectionPage):
|
class IncomingInspectionPage(BaseInspectionPage):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
@ -241,8 +243,11 @@ class IncomingInspectionPage(BaseInspectionPage):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.last_gch = gch
|
self.last_gch = gch
|
||||||
data, err = self.db_manager.query_by_gch(gch)
|
logger.info(f"查询工程号: {gch}")
|
||||||
|
with log_timing_context(f"query_by_gch({gch})", "incoming_inspection"):
|
||||||
|
data, err = self.db_manager.query_by_gch(gch)
|
||||||
if err:
|
if err:
|
||||||
|
logger.warning(f"查询失败: {err}")
|
||||||
self.show_info("查询失败", err)
|
self.show_info("查询失败", err)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -252,7 +257,6 @@ class IncomingInspectionPage(BaseInspectionPage):
|
|||||||
self.inputs["spec1"].setText(data.get("qy_czt") or "")
|
self.inputs["spec1"].setText(data.get("qy_czt") or "")
|
||||||
self.inputs["info1"].setText(data.get("heat_number2") or "")
|
self.inputs["info1"].setText(data.get("heat_number2") or "")
|
||||||
self.inputs["spec2"].setText(data.get("qy_czw") or "")
|
self.inputs["spec2"].setText(data.get("qy_czw") or "")
|
||||||
|
|
||||||
def update_ui_with_data(self, data):
|
def update_ui_with_data(self, data):
|
||||||
"""
|
"""
|
||||||
根据数据库数据更新UI。
|
根据数据库数据更新UI。
|
||||||
@ -294,7 +298,8 @@ class IncomingInspectionPage(BaseInspectionPage):
|
|||||||
if not hn1 and not hn2:
|
if not hn1 and not hn2:
|
||||||
return
|
return
|
||||||
|
|
||||||
heat1_range, heat2_range, err = self.db_manager.query_standard_range(hn1, hn2)
|
with log_timing_context("query_standard_range", "incoming_inspection"):
|
||||||
|
heat1_range, heat2_range, err = self.db_manager.query_standard_range(hn1, hn2)
|
||||||
if err:
|
if err:
|
||||||
self.show_info("标准值查询失败", err)
|
self.show_info("标准值查询失败", err)
|
||||||
return
|
return
|
||||||
@ -334,7 +339,7 @@ class IncomingInspectionPage(BaseInspectionPage):
|
|||||||
min_val = float(min_val) if min_val is not None and min_val != '' else None
|
min_val = float(min_val) if min_val is not None and min_val != '' else None
|
||||||
max_val = float(max_val) if max_val is not None and max_val != '' else None
|
max_val = float(max_val) if max_val is not None and max_val != '' else None
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
print(e)
|
logger.debug(f"标准值解析异常: {e}")
|
||||||
is_out = False
|
is_out = False
|
||||||
if min_val is not None and value < min_val:
|
if min_val is not None and value < min_val:
|
||||||
is_out = True
|
is_out = True
|
||||||
|
|||||||
@ -8,6 +8,8 @@ from ui.incoming_inspection_page import IncomingInspectionPage
|
|||||||
from ui.manual_inspection_page import ManualInspectionPage
|
from ui.manual_inspection_page import ManualInspectionPage
|
||||||
from src.db_manager import DatabaseManager
|
from src.db_manager import DatabaseManager
|
||||||
from src.session_manager import SessionManager
|
from src.session_manager import SessionManager
|
||||||
|
from utils.logger import get_logger, log_timing_context
|
||||||
|
logger = get_logger("inspection_window")
|
||||||
|
|
||||||
class InspectionWindow(QMainWindow):
|
class InspectionWindow(QMainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -116,6 +118,7 @@ class InspectionWindow(QMainWindow):
|
|||||||
|
|
||||||
self.is_loading = True
|
self.is_loading = True
|
||||||
self.loading_label.show()
|
self.loading_label.show()
|
||||||
|
logger.debug(f"触发加载更多数据, page={self.current_page}")
|
||||||
|
|
||||||
# 使用 QTimer 异步加载,避免阻塞 UI
|
# 使用 QTimer 异步加载,避免阻塞 UI
|
||||||
QTimer.singleShot(100, self._fetch_data)
|
QTimer.singleShot(100, self._fetch_data)
|
||||||
@ -124,9 +127,11 @@ class InspectionWindow(QMainWindow):
|
|||||||
"""从数据库获取数据"""
|
"""从数据库获取数据"""
|
||||||
offset = self.current_page * self.page_size
|
offset = self.current_page * self.page_size
|
||||||
user_id = self.session_manager.get_user_id() or ''
|
user_id = self.session_manager.get_user_id() or ''
|
||||||
rows, error = self.db_manager.get_inspection_list(self.page_size, offset, user_id=user_id)
|
with log_timing_context(f"get_inspection_list(page={self.current_page})", "inspection_window"):
|
||||||
|
rows, error = self.db_manager.get_inspection_list(self.page_size, offset, user_id=user_id)
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
|
logger.error(f"加载失败: {error}")
|
||||||
self.loading_label.setText(f"加载失败: {error}")
|
self.loading_label.setText(f"加载失败: {error}")
|
||||||
self.is_loading = False
|
self.is_loading = False
|
||||||
QTimer.singleShot(2000, lambda: self.loading_label.hide())
|
QTimer.singleShot(2000, lambda: self.loading_label.hide())
|
||||||
@ -141,6 +146,7 @@ class InspectionWindow(QMainWindow):
|
|||||||
self.add_card(dict(row))
|
self.add_card(dict(row))
|
||||||
|
|
||||||
self.current_page += 1
|
self.current_page += 1
|
||||||
|
logger.debug(f"加载了 {len(rows)} 条记录, 当前页={self.current_page}")
|
||||||
|
|
||||||
if len(rows) < self.page_size:
|
if len(rows) < self.page_size:
|
||||||
self.has_more_data = False
|
self.has_more_data = False
|
||||||
|
|||||||
@ -6,6 +6,8 @@ from PySide6.QtCore import Qt, Signal
|
|||||||
from PySide6.QtGui import QFont
|
from PySide6.QtGui import QFont
|
||||||
import hashlib
|
import hashlib
|
||||||
from src.db_manager import DatabaseManager
|
from src.db_manager import DatabaseManager
|
||||||
|
from utils.logger import get_logger, log_timing_context
|
||||||
|
logger = get_logger("login")
|
||||||
|
|
||||||
|
|
||||||
class LoginWindow(QMainWindow):
|
class LoginWindow(QMainWindow):
|
||||||
@ -117,12 +119,16 @@ class LoginWindow(QMainWindow):
|
|||||||
QMessageBox.warning(self, "登录失败", "请输入账号和密码")
|
QMessageBox.warning(self, "登录失败", "请输入账号和密码")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
logger.info(f"用户登录尝试: {user_id}")
|
||||||
password_md5 = hashlib.md5(password.encode()).hexdigest()
|
password_md5 = hashlib.md5(password.encode()).hexdigest()
|
||||||
success, user_info = self.db.authenticate_user(user_id, password_md5)
|
with log_timing_context(f"authenticate_user({user_id})", "login"):
|
||||||
|
success, user_info = self.db.authenticate_user(user_id, password_md5)
|
||||||
if success:
|
if success:
|
||||||
|
logger.info(f"登录成功: {user_id}")
|
||||||
self.login_success.emit(user_info)
|
self.login_success.emit(user_info)
|
||||||
self.close()
|
self.close()
|
||||||
else:
|
else:
|
||||||
|
logger.warning(f"登录失败: {user_id} - {user_info}")
|
||||||
QMessageBox.warning(self, "登录失败", user_info)
|
QMessageBox.warning(self, "登录失败", user_info)
|
||||||
self.password_input.clear()
|
self.password_input.clear()
|
||||||
self.password_input.setFocus()
|
self.password_input.setFocus()
|
||||||
|
|||||||
102
utils/logger.py
Normal file
102
utils/logger.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import functools
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
_logger_initialized = False
|
||||||
|
|
||||||
|
LOG_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "logs")
|
||||||
|
LOG_FILE = os.path.join(LOG_DIR, "app.log")
|
||||||
|
|
||||||
|
SLOW_THRESHOLD = 1.0
|
||||||
|
CRITICAL_THRESHOLD = 5.0
|
||||||
|
|
||||||
|
LOG_FORMAT = "[%(asctime)s] [%(levelname)-8s] [%(name)s:%(funcName)s:%(lineno)d] %(message)s"
|
||||||
|
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging(level=logging.DEBUG):
|
||||||
|
global _logger_initialized
|
||||||
|
if _logger_initialized:
|
||||||
|
return
|
||||||
|
_logger_initialized = True
|
||||||
|
|
||||||
|
os.makedirs(LOG_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
root_logger = logging.getLogger("app")
|
||||||
|
root_logger.setLevel(level)
|
||||||
|
|
||||||
|
if root_logger.handlers:
|
||||||
|
return
|
||||||
|
|
||||||
|
formatter = logging.Formatter(LOG_FORMAT, datefmt=DATE_FORMAT)
|
||||||
|
|
||||||
|
file_handler = RotatingFileHandler(
|
||||||
|
LOG_FILE, maxBytes=5 * 1024 * 1024, backupCount=3, encoding="utf-8"
|
||||||
|
)
|
||||||
|
file_handler.setLevel(logging.DEBUG)
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setLevel(logging.INFO)
|
||||||
|
console_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
root_logger.addHandler(file_handler)
|
||||||
|
root_logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
|
||||||
|
def get_logger(name):
|
||||||
|
return logging.getLogger(f"app.{name}")
|
||||||
|
|
||||||
|
|
||||||
|
def log_timing(func=None, *, logger_name=None):
|
||||||
|
def decorator(fn):
|
||||||
|
_logger = get_logger(logger_name or fn.__module__)
|
||||||
|
|
||||||
|
@functools.wraps(fn)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
fn_name = fn.__qualname__
|
||||||
|
_logger.debug(f"[ENTER] {fn_name}")
|
||||||
|
start = time.perf_counter()
|
||||||
|
try:
|
||||||
|
result = fn(*args, **kwargs)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
_logger.error(f"[ERROR] {fn_name}: {e}", exc_info=True)
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
elapsed = time.perf_counter() - start
|
||||||
|
if elapsed >= CRITICAL_THRESHOLD:
|
||||||
|
_logger.critical(f"[SLOW!] {fn_name} took {elapsed:.3f}s (>={CRITICAL_THRESHOLD}s)")
|
||||||
|
elif elapsed >= SLOW_THRESHOLD:
|
||||||
|
_logger.warning(f"[SLOW] {fn_name} took {elapsed:.3f}s (>={SLOW_THRESHOLD}s)")
|
||||||
|
else:
|
||||||
|
_logger.debug(f"[EXIT] {fn_name} took {elapsed:.3f}s")
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
if func is not None:
|
||||||
|
return decorator(func)
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def log_timing_context(operation, logger_name=None):
|
||||||
|
_logger = get_logger(logger_name or "timing")
|
||||||
|
_logger.debug(f"[START] {operation}")
|
||||||
|
start = time.perf_counter()
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except Exception as e:
|
||||||
|
_logger.error(f"[ERROR] {operation}: {e}", exc_info=True)
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
elapsed = time.perf_counter() - start
|
||||||
|
if elapsed >= CRITICAL_THRESHOLD:
|
||||||
|
_logger.critical(f"[SLOW!] {operation} took {elapsed:.3f}s (>={CRITICAL_THRESHOLD}s)")
|
||||||
|
elif elapsed >= SLOW_THRESHOLD:
|
||||||
|
_logger.warning(f"[SLOW] {operation} took {elapsed:.3f}s (>={SLOW_THRESHOLD}s)")
|
||||||
|
else:
|
||||||
|
_logger.debug(f"[DONE] {operation} took {elapsed:.3f}s")
|
||||||
@ -1,5 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
from utils.logger import get_logger, log_timing
|
||||||
|
logger = get_logger("mtp")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import win32com.client
|
import win32com.client
|
||||||
@ -14,11 +16,12 @@ class MTPHandler:
|
|||||||
if not os.path.exists(self.cache_dir):
|
if not os.path.exists(self.cache_dir):
|
||||||
os.makedirs(self.cache_dir)
|
os.makedirs(self.cache_dir)
|
||||||
|
|
||||||
|
@log_timing
|
||||||
def get_latest_xml_windows(self, device_name, path_str):
|
def get_latest_xml_windows(self, device_name, path_str):
|
||||||
"""
|
"""
|
||||||
在 Windows MTP 设备上寻找并复制最新的 XML 文件。
|
在 Windows MTP 设备上寻找并复制最新的 XML 文件。
|
||||||
"""
|
"""
|
||||||
print(f"\n[MTP] >>> 开始同步工作: 设备={device_name}")
|
logger.info(f"开始同步工作: 设备={device_name}")
|
||||||
if win32com is None:
|
if win32com is None:
|
||||||
return None, "系统未安装 pywin32。"
|
return None, "系统未安装 pywin32。"
|
||||||
|
|
||||||
@ -31,7 +34,7 @@ class MTPHandler:
|
|||||||
device_item = None
|
device_item = None
|
||||||
target_name = device_name.strip().lower()
|
target_name = device_name.strip().lower()
|
||||||
|
|
||||||
print(f"[MTP] 正在检索设备,目标名称: '{target_name}'")
|
logger.debug(f"正在检索设备,目标名称: '{target_name}'")
|
||||||
items = computer.Items()
|
items = computer.Items()
|
||||||
available_devices = []
|
available_devices = []
|
||||||
|
|
||||||
@ -39,12 +42,12 @@ class MTPHandler:
|
|||||||
available_devices.append(item.Name)
|
available_devices.append(item.Name)
|
||||||
if item.Name.strip().lower() == target_name:
|
if item.Name.strip().lower() == target_name:
|
||||||
device_item = item
|
device_item = item
|
||||||
print(f"[MTP] 匹配成功: '{item.Name}'")
|
logger.info(f"匹配成功: '{item.Name}'")
|
||||||
break
|
break
|
||||||
|
|
||||||
if not device_item:
|
if not device_item:
|
||||||
print(f"[MTP] 未找到设备: '{device_name}'")
|
logger.warning(f"未找到设备: '{device_name}'")
|
||||||
print(f"[MTP] 当前电脑识别到的设备列表: {available_devices}")
|
logger.warning(f"当前电脑识别到的设备列表: {available_devices}")
|
||||||
return None, f"未找到 MTP 设备: {device_name} (请检查名称)"
|
return None, f"未找到 MTP 设备: {device_name} (请检查名称)"
|
||||||
|
|
||||||
current_folder = device_item.GetFolder
|
current_folder = device_item.GetFolder
|
||||||
@ -59,20 +62,20 @@ class MTPHandler:
|
|||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
if not found:
|
if not found:
|
||||||
print(f"[MTP] 路径中断,找不到文件夹: '{folder_name}'")
|
logger.warning(f"路径中断,找不到文件夹: '{folder_name}'")
|
||||||
# 打印一下当前目录下有的东西,帮用户排查
|
# 打印一下当前目录下有的东西,帮用户排查
|
||||||
content = [i.Name for i in current_folder.Items()]
|
content = [i.Name for i in current_folder.Items()]
|
||||||
print(f"[MTP] 当前所在位置 [ {current_folder.Title} ] 下的可选内容: {content}")
|
logger.debug(f"当前所在位置 [ {current_folder.Title} ] 下的可选内容: {content}")
|
||||||
return None, f"路径点不存在: {folder_name}"
|
return None, f"路径点不存在: {folder_name}"
|
||||||
|
|
||||||
# 3. 扫描文件并识别“最新”
|
# 3. 扫描文件并识别“最新”
|
||||||
print(f"[MTP] 已到达目标目录: [ {current_folder.Title} ]")
|
logger.info(f"已到达目标目录: [ {current_folder.Title} ]")
|
||||||
print(f"[MTP] 正在列出该目录下所有项目...")
|
logger.debug(f"正在列出该目录下所有项目...")
|
||||||
print("-" * 40)
|
logger.debug("-" * 40)
|
||||||
|
|
||||||
xml_items = []
|
xml_items = []
|
||||||
all_items = current_folder.Items()
|
all_items = current_folder.Items()
|
||||||
print(f"[MTP] 目录内总项数: {all_items.Count}")
|
logger.debug(f"目录内总项数: {all_items.Count}")
|
||||||
|
|
||||||
for i in range(all_items.Count):
|
for i in range(all_items.Count):
|
||||||
try:
|
try:
|
||||||
@ -83,7 +86,7 @@ class MTPHandler:
|
|||||||
# 打印每一个文件的详细信息用于排查
|
# 打印每一个文件的详细信息用于排查
|
||||||
# 尝试打印前 6 个详情列,看看哪一列是类型,哪一列是时间
|
# 尝试打印前 6 个详情列,看看哪一列是类型,哪一列是时间
|
||||||
details = [str(current_folder.GetDetailsOf(item, j)) for j in range(6)]
|
details = [str(current_folder.GetDetailsOf(item, j)) for j in range(6)]
|
||||||
if i == 0: print(f"[MTP] 属性列参考(0-5): {details}")
|
if i == 0: logger.debug(f"属性列参考(0-5): {details}")
|
||||||
|
|
||||||
item_type = details[2]
|
item_type = details[2]
|
||||||
# 识别逻辑:
|
# 识别逻辑:
|
||||||
@ -106,16 +109,16 @@ class MTPHandler:
|
|||||||
'time': mtime,
|
'time': mtime,
|
||||||
'name_ts': name_ts
|
'name_ts': name_ts
|
||||||
})
|
})
|
||||||
print(f"[MTP] 识别 XML: '{name}' | 创建/修改时间: {mtime} | 文件名时间: {name_ts}")
|
logger.debug(f"识别 XML: '{name}' | 创建/修改时间: {mtime} | 文件名时间: {name_ts}")
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[MTP] 读取第 {i} 个项目时出错: {e}")
|
logger.error(f"读取第 {i} 个项目时出错: {e}")
|
||||||
|
|
||||||
print("-" * 40)
|
logger.debug("-" * 40)
|
||||||
|
|
||||||
if not xml_items:
|
if not xml_items:
|
||||||
print(f"[MTP] 报错: 在目录 [ {current_folder.Title} ] 中没有找到任何以 .xml 结尾的文件")
|
logger.warning(f"在目录 [ {current_folder.Title} ] 中没有找到任何以 .xml 结尾的文件")
|
||||||
return None, "该目录中没有 XML 文件"
|
return None, "该目录中没有 XML 文件"
|
||||||
|
|
||||||
# 排序逻辑优化:
|
# 排序逻辑优化:
|
||||||
@ -128,9 +131,9 @@ class MTPHandler:
|
|||||||
latest_item = latest['item']
|
latest_item = latest['item']
|
||||||
filename = latest['name']
|
filename = latest['name']
|
||||||
|
|
||||||
print(f"[MTP] 选定最新文件: {filename}")
|
logger.info(f"选定最新文件: {filename}")
|
||||||
print(f"[MTP] - 属性时间: {latest['time']}")
|
logger.debug(f" - 属性时间: {latest['time']}")
|
||||||
print(f"[MTP] - 名称日期: {latest['name_ts']}")
|
logger.debug(f" - 名称日期: {latest['name_ts']}")
|
||||||
|
|
||||||
# 4. 复制到本地
|
# 4. 复制到本地
|
||||||
local_path = os.path.abspath(os.path.join(self.cache_dir, filename))
|
local_path = os.path.abspath(os.path.join(self.cache_dir, filename))
|
||||||
@ -140,7 +143,7 @@ class MTPHandler:
|
|||||||
try: os.remove(os.path.join(self.cache_dir, f))
|
try: os.remove(os.path.join(self.cache_dir, f))
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
print(f"[MTP] 正在拉取文件到本地缓存...")
|
logger.info(f"正在拉取文件到本地缓存...")
|
||||||
dest_shell_folder = shell.NameSpace(os.path.abspath(self.cache_dir))
|
dest_shell_folder = shell.NameSpace(os.path.abspath(self.cache_dir))
|
||||||
dest_shell_folder.CopyHere(latest_item, 4 | 16)
|
dest_shell_folder.CopyHere(latest_item, 4 | 16)
|
||||||
|
|
||||||
@ -153,13 +156,13 @@ class MTPHandler:
|
|||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
print(f"[MTP] ✨ 同步成功: {filename}")
|
logger.info(f"同步成功: {filename}")
|
||||||
return local_path, filename
|
return local_path, filename
|
||||||
else:
|
else:
|
||||||
return None, "同步超时"
|
return None, "同步超时"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[MTP] 异常: {e}")
|
logger.error(f"MTP异常: {e}", exc_info=True)
|
||||||
return None, str(e)
|
return None, str(e)
|
||||||
finally:
|
finally:
|
||||||
pythoncom.CoUninitialize()
|
pythoncom.CoUninitialize()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user