🧪 第1章 建立最基础的应用程序
📄 一句话概括

flash 的消息存在会话里,读一次就删,所以它只活一跳。 日志五级:DEBUG、INFO、WARNING、ERROR、CRITICAL;setLevel 定的是下限。 发邮件这件事本沙箱只做到「组装」,发送要 SMTP 账号,照实标。

📝 本章和前面几章的关系

这一篇对应教材 1.3 节的 Flash、日志、邮件三小节,是「反馈」这一组的三件套。 上一篇收数据,这一篇告诉用户结果,同时留下给运维看的东西。

一、flash 解决什么问题

用户点了「保存」,页面跳回列表。这时候得告诉他一声「保存成功了」。 但跳转是重定向,跳过去那个页面并不知道刚才发生了什么。把消息塞进 URL?难看、能被改、还会被收藏。 flash 的做法是:消息存进会话,下一跳读出来就删。正好一跳,多一次刷新就没了。

这既解决了「跨请求传消息」,又天然避免了「同一个提示反复弹」。

二、两段式用法

写的那一头:flash("保存成功", "success"),第二个参数是分类。 读的那一头:模板里调 get_flashed_messages(with_categories=true),带分类就要解二元组。 分类只是个字符串,作用是在模板里挂不同的 CSS 类。常见的是 success、warning、error 三个。

实测:跟过去那次请求能看到两条,再刷一次就空了——这就是「读一次就删」的直接证据。

三、日志五级怎么选

级别 用来记什么
DEBUG 排查用的细节,比如「这里进了哪个分支」
INFO 正常但值得记的事件,比如「用户登录了」
WARNING 能继续跑,但有点不对,比如「配置缺项,用了默认值」
ERROR 功能失败了,比如「调用支付接口超时」
CRITICAL 整个服务不能跑了

setLevel 定的是下限:设成 WARNING,INFO 和 DEBUG 就直接不产生。 这一点很好用:开发时设 DEBUG 看细节,上线设 INFO 或 WARNING,同一份代码两种噪音。 app.logger 是 Flask 配好的 logger,名字就是应用名。 别自己 logging.getLogger() 另起一个,那样格式和去向都不统一。

四、日志去哪了

默认输出到 stderr。容器里 stderr 会被收集,所以最省事的做法就是「什么都不配,让它往 stderr 写」。 要落文件就得自己加 FileHandler,但要注意两件事:文件会一直长,而且多进程同时写会打架。 所以生产环境通常是「输出到 stdout/stderr,由外部系统收集」,不是自己写文件。

教材用 RotatingFileHandler 演示按大小切分,那是单机部署的写法,知道有这回事就行。

五、发邮件:能测的和不能测的

教材 1.3.6 讲用 smtplib 发邮件:组装 EmailMessage,连 SMTP 服务器,登录,发送。 组装和发送是两件事。组装纯本地,能测;发送要服务器和账号,测不了。 这次实测能真跑的是组装:设好主题、发件人、收件人、正文,序列化出来 217 字节,全是标准 MIME 格式。

发送那一段(SMTP_SSL 加 login)照原样贴出并标注不执行——没有账号密码,假装跑过没有意义。

六、动手 Demo:Flash 消息与日志记录

这一节把 flash 的一跳、日志五级、级别过滤三件事各跑一遍,最后组装一封不发送的邮件。

@app.route("/flash")
def do_flash():
    flash("保存成功", "success")
    return redirect(url_for("show"))

@app.route("/show")
def show():
    return render_template("msg.html")

实跑输出:

--------------------------------------------------------------------
Flash 消息与日志记录
--------------------------------------------------------------------

  ① flash 只活一跳
  --------------------------------------------------------------------
  GET /flash -> 302,跳转到 /show
  跟过去 GET /show,页面内容:
  [success] 保存成功
  [warning] 有 2 条记录没填完整


  (页面渲染完毕)
  再刷一次 /show -> (页面渲染完毕)
  ! 第二次就空了。flash 的消息存在会话里,读一次就删——这正是「提示不许重复出现」的实现方式。

  ② 不带 category 会怎样
  --------------------------------------------------------------------
  get_flashed_messages() -> ['没有分类的消息']
  with_categories=True -> [('message', '没有分类的消息')]
  分类就是个字符串,用来在模板里挂不同的 CSS 类。success 绿、warning 黄、error 红。

  ③ 日志:五级与去向
  --------------------------------------------------------------------
  DEBUG _d62b 这是一条 DEBUG 级别的日志
  INFO _d62b 这是一条 INFO 级别的日志
  WARNING _d62b 这是一条 WARNING 级别的日志
  ERROR _d62b 这是一条 ERROR 级别的日志
  CRITICAL _d62b 这是一条 CRITICAL 级别的日志
  ! 五级从轻到重:DEBUG、INFO、WARNING、ERROR、CRITICAL。
  setLevel 定的是「这个级别及以上才输出」,级别以下的直接丢掉。

  ④ 级别真的会挡
  --------------------------------------------------------------------
  把级别调到 WARNING 之后,缓冲区里只有:'这条会'
  ! 上线时级别调高、开发时调低,同一份代码两种噪音。

  ⑤ 发邮件:这一步本沙箱不执行
  --------------------------------------------------------------------

  [平台原样 · 本沙箱不执行]
  | import smtplib
  | from email.message import EmailMessage
  |
  | msg = EmailMessage()
  | msg["Subject"] = "测试"
  | msg["From"] = "[email protected]"
  | msg["To"] = "[email protected]"
  | msg.set_content("正文")
  |
  | with smtplib.SMTP_SSL("smtp.example.com", 465) as s:
  |     s.login("账号", "应用专用密码")
  |     s.send_message(msg)
  要一台真的 SMTP 服务器和一个能用的账号密码,本沙箱两样都没有。

  能真跑的部分是「把邮件组装出来」:
  Subject 头 -> 测试邮件
  正文长度 -> 217 字节
  ! 组装和发送是两件事。组装能测、发送要账号——所以先把组装做好,发送部分等有了 SMTP 再验。

第一次跟过去能看到两条,第二次刷新只剩「页面渲染完毕」——读一次就删,这条最该记住。 五级日志用的格式串是级别、logger 名、消息,前两项不是手写的。 调到 WARNING 之后,缓冲区里只剩「这条会」——级别以下的不产生,不是产生后过滤。 邮件那一段组装出 217 字节,能验的就是这些;发送需要账号,标了不执行。

30 秒实验

  1. 把 flash 的分类换成自定义的 error,模板里什么都不改,看输出变成什么样。
  2. 把日志格式串里加上 %(asctime)s,看会不会多出时间戳。
  3. 想一想:如果一个请求里 flash 了三次,get_flashed_messages() 会返回几个?

常见误区

  1. 以为 flash 会一直显示:它只活一跳,刷新就没了,这不是 bug。
  2. 用 print 打日志:没级别、没时间、线上关不掉。
  3. 日志里写密码和令牌:日志会被收集、转发、长期保存,等于二次泄露。
  4. 以为所有日志都要落文件:容器里输出到 stderr 由外部收集更稳。
自测三问(点开对答案) 1. flash 的消息存哪、活多久? —— 存在会话里,下一跳读出来就删,所以只活一次请求。 2. `setLevel(WARNING)` 之后 DEBUG 日志去哪了? —— 根本不产生——级别是下限,低于它的直接丢掉。 3. 为什么邮件只做到组装? —— 发送需要真实的 SMTP 服务器和账号密码,沙箱里两样都没有,假装跑过没有意义。

反馈有了,还差「记住你是谁」。下一篇讲 Cookie 与 Session,顺带把 Response 这个真正的返回值讲清。

相关笔记