Python 的 logging.basicConfig 有一个常见坑:一旦指定了 filename,默认只写文件,不往控制台打印。
问题复现
logging.basicConfig(
filename="app.log",
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s"
)
print("print 能看到")
logging.info("logger 看不到") # 只写文件,控制台不显示
同时输出到文件和控制台
需要显式添加 StreamHandler:
import logging
import sys
from datetime import datetime
def init_logger(log_file="app.log"):
logger = logging.getLogger()
logger.setLevel(logging.INFO)
fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
# 文件输出
fh = logging.FileHandler(log_file, encoding="utf-8")
fh.setFormatter(fmt)
logger.addHandler(fh)
# 控制台输出
ch = logging.StreamHandler(sys.stdout)
ch.setFormatter(fmt)
logger.addHandler(ch)
return logger
或者用 basicConfig + 手动追加:
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
filename="app.log",
encoding="utf-8"
)
# 追加控制台 Handler
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
logging.getLogger().addHandler(console)
带结构化参数的 log 方法
class MyService:
def __init__(self):
self.logger = logging.getLogger(self.__class__.__name__)
def log(self, *args, **kwargs):
msg = " ".join(str(a) for a in args)
if kwargs:
extra = " ".join(f"{k}={v!r}" for k, v in kwargs.items())
msg = f"{msg} | {extra}"
self.logger.info(msg)
# 使用
svc = MyService()
svc.log("任务完成", task_id=42, duration=1.5)
# 输出: 任务完成 | task_id=42 duration=1.5
PyInstaller —noconsole 下看不到日志
用 --noconsole 打包的 exe 绑定到 Windows subsystem,stdout / stderr 不存在,StreamHandler 无输出目标。
解决方案:
1. 只写文件,用 tail 等工具实时查看
# PowerShell(Windows 自带,等价于 tail -f)
Get-Content app.log -Tail 50 -Wait
2. 自定义 GUI 日志窗口(Tkinter)
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
class TextHandler(logging.Handler):
def __init__(self, widget):
super().__init__()
self.widget = widget
def emit(self, record):
msg = self.format(record)
self.widget.insert(tk.END, msg + "\n")
self.widget.see(tk.END)
root = tk.Tk()
text = ScrolledText(root, width=80, height=20)
text.pack()
handler = TextHandler(text)
handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
logging.getLogger().addHandler(handler)
logging.info("程序启动")
root.mainloop()
3. 调试 / 发布双版本
:: build.bat
pyinstaller --onefile --console --name=控制器_debug scripts.py
pyinstaller --onefile --noconsole --name=控制器 scripts.py
开发用 debug 版,用户用 release 版。
