129 lines
3.8 KiB
Python
129 lines
3.8 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
EXE文件日志器 - 将所有输出重定向到文件
|
|||
|
|
适用于 pyinstaller -w 打包的exe
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import sys
|
|||
|
|
import os
|
|||
|
|
from datetime import datetime
|
|||
|
|
import threading
|
|||
|
|
|
|||
|
|
class FileLogger:
|
|||
|
|
"""文件日志器类"""
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
# 确定日志文件路径
|
|||
|
|
if getattr(sys, 'frozen', False):
|
|||
|
|
# 打包环境
|
|||
|
|
self.log_dir = os.path.dirname(sys.executable)
|
|||
|
|
else:
|
|||
|
|
# 开发环境
|
|||
|
|
self.log_dir = os.getcwd()
|
|||
|
|
|
|||
|
|
# 创建日志文件
|
|||
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|||
|
|
self.log_file = os.path.join(self.log_dir, f"qianniu_exe_{timestamp}.log")
|
|||
|
|
|
|||
|
|
# 线程锁,确保多线程写入安全
|
|||
|
|
self.lock = threading.Lock()
|
|||
|
|
|
|||
|
|
# 初始化日志文件
|
|||
|
|
self.write_log("=" * 80)
|
|||
|
|
self.write_log("千牛EXE日志开始")
|
|||
|
|
self.write_log(f"Python版本: {sys.version}")
|
|||
|
|
self.write_log(f"是否打包环境: {getattr(sys, 'frozen', False)}")
|
|||
|
|
self.write_log(f"日志文件: {self.log_file}")
|
|||
|
|
self.write_log("=" * 80)
|
|||
|
|
|
|||
|
|
def write_log(self, message):
|
|||
|
|
"""写入日志到文件"""
|
|||
|
|
try:
|
|||
|
|
with self.lock:
|
|||
|
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
|||
|
|
log_entry = f"[{timestamp}] {message}\n"
|
|||
|
|
|
|||
|
|
with open(self.log_file, 'a', encoding='utf-8') as f:
|
|||
|
|
f.write(log_entry)
|
|||
|
|
f.flush()
|
|||
|
|
os.fsync(f.fileno()) # 强制写入磁盘
|
|||
|
|
except Exception as e:
|
|||
|
|
# 如果写入失败,至少尝试输出到stderr
|
|||
|
|
try:
|
|||
|
|
sys.stderr.write(f"LOG_ERROR: {message} | Error: {e}\n")
|
|||
|
|
sys.stderr.flush()
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
def write(self, text):
|
|||
|
|
"""实现write方法以支持作为sys.stdout使用"""
|
|||
|
|
if text.strip(): # 只记录非空内容
|
|||
|
|
self.write_log(text.strip())
|
|||
|
|
|
|||
|
|
def flush(self):
|
|||
|
|
"""实现flush方法"""
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
class TeeOutput:
|
|||
|
|
"""同时输出到原始输出和文件的类"""
|
|||
|
|
|
|||
|
|
def __init__(self, original, file_logger):
|
|||
|
|
self.original = original
|
|||
|
|
self.file_logger = file_logger
|
|||
|
|
|
|||
|
|
def write(self, text):
|
|||
|
|
# 写入原始输出(如果存在)
|
|||
|
|
if self.original:
|
|||
|
|
try:
|
|||
|
|
self.original.write(text)
|
|||
|
|
self.original.flush()
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# 写入文件日志
|
|||
|
|
if text.strip():
|
|||
|
|
self.file_logger.write_log(text.strip())
|
|||
|
|
|
|||
|
|
def flush(self):
|
|||
|
|
if self.original:
|
|||
|
|
try:
|
|||
|
|
self.original.flush()
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# 全局文件日志器实例
|
|||
|
|
_file_logger = None
|
|||
|
|
|
|||
|
|
def setup_file_logging():
|
|||
|
|
"""设置文件日志记录"""
|
|||
|
|
global _file_logger
|
|||
|
|
|
|||
|
|
if _file_logger is None:
|
|||
|
|
_file_logger = FileLogger()
|
|||
|
|
|
|||
|
|
# 保存原始的stdout和stderr
|
|||
|
|
original_stdout = sys.stdout
|
|||
|
|
original_stderr = sys.stderr
|
|||
|
|
|
|||
|
|
# 创建Tee输出,同时输出到原始输出和文件
|
|||
|
|
sys.stdout = TeeOutput(original_stdout, _file_logger)
|
|||
|
|
sys.stderr = TeeOutput(original_stderr, _file_logger)
|
|||
|
|
|
|||
|
|
_file_logger.write_log("文件日志系统已设置完成")
|
|||
|
|
|
|||
|
|
return _file_logger
|
|||
|
|
|
|||
|
|
def log_to_file(message):
|
|||
|
|
"""直接写入文件日志的函数"""
|
|||
|
|
global _file_logger
|
|||
|
|
if _file_logger:
|
|||
|
|
_file_logger.write_log(message)
|
|||
|
|
else:
|
|||
|
|
# 如果还没有初始化,先初始化
|
|||
|
|
setup_file_logging()
|
|||
|
|
_file_logger.write_log(message)
|
|||
|
|
|
|||
|
|
# 注释掉自动初始化,改为手动调用
|
|||
|
|
# if getattr(sys, 'frozen', False):
|
|||
|
|
# setup_file_logging()
|