技术手记

macOS 还是需要 Karabiner 来解决 CapsLock 和 Shift 延迟的问题

在前阵子的 macOS 实用小工具记录 2 里提到,因为有原生锁屏快捷键后不用装 Karabiner 了,但最近在 macOS 下打字,按 CapsLock 切换大小写或临时按 Shift 时总有延迟的感觉,搜到这个现象很普遍,但怎么解决不知道

今天看到 v2ex 这个帖 https://www.v2ex.com/t/851971 ,以及里面提到的几个其他讨论帖,还是把 Karabiner 装了回来,因为

How to disable caps lock delay
Karabiner-Elements disables the caps lock delay without any action since v13.3.0.

官网在 https://karabiner-elements.pqrs.org/

如果用 brew 则用 brew install karabiner-elements 安装

macOS 实用小工具记录 2

本文用于更新和补充 macOS 实用小工具记录 里提到的工具 v2022.03.16

更新部分

Hidden Bar

  • 最终发现不用也能接受,只要吧 Stats 显示的监控变少一点就好了,不用放那么多图标在菜单栏里

Karabiner

  • 如原文所说,之前是为了映射 Power 键方便锁屏,现在用原生 Ctrl+Cmd+Q 已经可以解决了,自己没有别的键映射就没再装了

Scroll Reverser

  • 这个工具的作者说未来会改为收费工具,虽然一直没有落地,但不想白嫖
  • 另外是这个工具在 macOS Monterey 下(macOS 12.x)偶尔会失效,找到了另一个工具来替代

补充部分

Cleaner

Keka

  • https://www.keka.io
  • 压缩解压工具,命令行版的 p7zip 对非专业用户还是不够友好

MOS

  • https://mos.caldis.me/
  • 替代 Scroll Reverser 的小工具,将鼠标的滚轮方向单独调整成跟 Windows 下方向一样,保留触摸板是原生方向
  • 在 GitHub 上有免费开源,也可以 brew 安装,界面看起来更现代化一些

macOS 实用小工具记录

记录一下自己目前在 macOS 上常用的小工具们(按字母序)v2022.02.10

DaisyDisk

  • https://daisydiskapp.com/
  • 磁盘空间分析工具,免费可试用,我某次买过付费的就一直在用
  • 可以用 gdu 等工具替代,只是这个可视化做的更漂亮

displayplacer

EasyRes

  • http://easyresapp.com/
  • 屏幕分辨率快速管理工具,免费,Mac App Store (MAS) 直接有下载
  • 相比 RDM 等工具,这个安装和使用更方便

Hidden Bar

Itsycal

Karabiner

  • https://karabiner-elements.pqrs.org/
  • 键位重映射工具,之前主要用来把外接键盘的 Pause/Break 重置为 Power 方便锁屏,现在有了原生 Ctrl+Cmd+Q 后好像用不上了

Rectangle

  • https://rectangleapp.com/
  • 窗口布局管理工具,之前的 Spectacle 不更新了,这个适配新的更好点,同样免费且开源

Scroll Reverser

  • https://pilotmoon.com/scrollreverser/
  • 鼠标滚动方向调整小工具,系统原生只能触摸板和鼠标方向同时改,实际上触摸板保留 macOS 原生方向跟手挺好,外接鼠标还是跟 Win 保持一致更舒服

Snipaste

  • https://www.snipaste.com/
  • 截图小工具,也有推荐 Shottr 的,不过这个在 macOS/Win 上都有,国人开发界面也更好理解

Stats

近期用电脑的几个小技巧调整

Edge 浏览器在某个版本后默认开启了拷贝当前访问网页链接时,复制的是网页标题,粘贴时如果遇到富文本编辑器,出来是标题文字加跳转链接,如果遇到纯文本编辑器,出来就是网页标题。这个可以在 Edge 的设置里「共享、复制和粘贴」里将格式改成「纯文本」来解决

macOS 12 Monterey 里新增了快捷备忘录的操作,默认是屏幕右下角作为触发角,以及 Fn+Q 唤起。这个本是个好事,但增加了触发角其实会有不必要的困扰,在 macOS 的系统偏好设置里,到「桌面与屏幕保护程序」里,在屏幕保护程序页的右下角来设置「触发角」将其改为没有任何行为即可。顺带吐槽下触发角这个设置项的入口其实挺反直觉的,好在还能有搜索

MacBook Pro 通过 BootCamp 安装 Windows 10 及取消 Office365 链接 OneDrive

公司有一批换旧下来的 MacBook Pro,同时因为一些调试需求,又需要 Windows 设备,看了下公司买的 Windows 10 Pro 授权是有多的,可以通过 BootCamp 在这些机器上装 Win10 来当测试机用

BootCamp 在 macOS 里自带

准备好 Win10 的安装文件,因为我们有买企业授权,直接去批量许可服务中心去下载对应 ISO 文件

这里有踩一个坑,在 macOS Catalina (10.15) 之前版本,BootCamp 可能提示复制 ISO 文件失败,这里需要使用一个叫 Boot Camp ISO Convert 的工具,将原版 ISO 做一下转换。简单说原因就是 FAT32 文件系统不支持单个文件超过 4GiB,转换工具可以将 ISO 里的 install.wim 转成更小的文件,工具下载链接在 https://twocanoes-software-updates.s3.amazonaws.com/Boot%20Camp%20ISO%20Converter1_6.dmg

按 BootCamp 提示,选择转换好后的 ISO 文件,确定分区大小,直接下一步就好了。重启进入 Windows 安装流程可能发现没有网络,先跳过,等完成安装后在资源管理器里看有一个虚拟光驱,里面是 Win10 安装文件,进入发现里面相比原版安装镜像多了个 Boot Camp 工具包文件夹,进去 setup.exe 安装好各种驱动,再重启一次就完成安装

目前看 Win10 对 MacBook Pro 的高分屏支持也还不错,除了安装过程一开始只能 100% DPI,后面的安装流程和系统内,200% DPI 下显示效果很棒

因为是测试机,登录企业 Office 365 后不希望跟账户绑定的 OneDrive 和 OneDrive for Business 打通,先在系统设置里卸载 OneDrive,然后在注册表里到 计算机\HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Common\Internet 路径下,新增一个名为 OnlineStorage 的 DWORD 键值,数值写 3,关闭所有 Office 应用再重新打开,在个人账户页和打开页就都没有 OneDrive 在线内容了

macOS 下维持鼠标滚动方向和 Windows 一致

macOS 有很多神奇的小细节会让 Win/macOS 双持党会感觉别扭,比如 macOS 的触摸板滚动方向是用了一个「自然」来描述,就是想象你摁着触摸板当一张纸在移动,方向跟 Windows 是反的

这个其实本不是问题,用 MacBook Pro 时按苹果的逻辑来就好,但是当外接鼠标时,还期望鼠标的滚轮方向跟 Windows 一样,就比较麻烦,修改 macOS 的滚动设置,只能触摸板和鼠标的方向同时改

我用的罗技和赛睿的鼠标都有厂商特定的工具,设置了后可以做到只修改鼠标的垂直滚动方向而不影响触摸板。罗技的叫 Logitech Options,之前都能正常工作,最近不知道是升级了 10.15.4 还是别的啥原因,这货工作不正常了,卸载重装也试了,在 macOS 的安全隐私里重新设置过也没用

各种搜索后发现了这么一个小工具 https://pilotmoon.com/scrollreverser/ ,下载试用感觉良好,不像罗技那个应用非常大,这个工具只用了 1M 不到就解决了问题,而且不仅限于特定品牌的鼠标

屠龙少年终变龙 从 Chrome 切换成 Edge

犹记得 2007 年在 Google 上海实习时,看到当时还没公开发布的 Chrome,还是那个蓝底 Tab 栏的早期版本,简洁清爽。相比之下 IE6 那时候已经被吐槽的很厉害,似乎 Vista 也只是自带了 IE7,然后因为吃硬件也没普及,我那时候似乎用的是遨游(Maxthon),随着遨游 2 的发布也愈显臃肿

过了十多年,当年大家吐槽的各种 IE only 甚至 IE6 only,变成了现在的 webkit only 甚至 chrome only,微软也放弃了自己的 IE 内核 Trident,新版的 Edge 浏览器最终还是基于 Chromium,又一个天下大势分久必合合久必分的循环

Chrome 被 Google 变得越来越私有化,各种更新和决策也完全是 Google 的一己私好,界面不知道是正大光明的为了对移动端和触屏更友好,还是单纯的一帮 UI 想彰显存在感,改的也越来越莫名其妙,最终,因为最近几个版本的 Chrome,在从别的应用冷启动唤起时,必然会页面全挂,且后续页面都会跟着挂,于是考虑迁移到 Microsoft Edge 上

在 Win10 1909 和 macOS 10.15 上都完成迁移,比想象中的无痛,相关的收藏夹密码记录等可以无缝导入,只是 Google 的账号同步变成了微软的账号同步,平常需要的插件在 Edge 的应用商店里也有,几乎就没变化。上周开始办公和家用所有电脑的默认浏览器都改成 Edge 了

到目前 Edge 还有一些小细节期望得到改善,不过应该都是在不久未来版本里会实现的

  • 搜索引擎自动发现。目前 Edge 还需要各种手动添加各网站自己的搜索,比如 taobao 我访问并搜索过后,下次并不能直接在地址栏里用淘宝的搜索,必须自己手动添加过
  • 访问记录跨设备同步
  • 下载过程可视化。现在只有下载完成后才在窗口下部跟 Chrome 一样有下载完成的提示,下载中的似乎默认不可见,需要打开下载管理页才能看到

记一次诡异的被 yarn 坑的过程

事情的起源是 @tdzl2003 说他用 VSCode 的 remote 模式用的很开心,放弃 Hyper-V 回到 VirtualBox 的怀抱,笨狗去看了下 remote 相关功能好像还在 VSCode Insider 里,可以装来试试看,弄好后看了下文档和教程,知道插件分装在本地和装在远端,但是项目里 eslint 插件就一直在报错,说找不到模块

找不到就去补吧,到远端的目录下 yarn install 装好依赖,回到本地一看,还是报错,怎么会这样?在远端也执行下看看?

$ ./node_modules/.bin/eslint index.js
Error: Cannot find module 'jsx-ast-utils/elementType'
...

What the fuck?! 一定是哪里不对,把 jsx-ast-utils 这个包也重装一下?再来

$ yarn add jsx-ast-utils
$ ./node_modules/.bin/eslint index.js
Error: Cannot find module 'jsx-ast-utils/elementType'
...

你特么是在逗我?去看看这个包到底怎么回事,里面只有 lib src 两个目录,其他都没有,看起来好像是不太对

确认了一下环境,Windows 10 Pro 1903, WSL Ubuntu 18.04.2, node 12.3.1, yarn 1.16.0,都是最新的,换个环境试试看?在自己另一台 MacBook Pro 上 yarn install 后就是正常的,hmmm,难道是 Windows 目录挂载到 WSL 下后文件权限啥的锅?试了下在 WSL 里用 WSL 的文件系统重新来过,问题依旧。在 Windows 里把 WSL 映射成网络盘,用 PowerShell 进到 WSL 文件系统里 yarn install 就是好的,嗯?好了?回到 WSL 下执行 PowerShell 安装好的 node_modules 也成功

难道是 WSL 的奇怪问题,搜了下好像也没有任何相关信息?难道是最近新崩的问题所以大家都找不到,我印象中之前确实也能跑来着?那就是这个锅了。等几乎认定就是 WSL 有奇怪的问题后,晚上回家后拿另一台同样环境的机器,在 WSL 下就正常的装好了

这这这,等会,冷静一下,一定有哪里不对,要不用下重启大法?把所有相关的东西都干掉重来一下?在有问题的机器上,把 git 仓库清空重来(虽然之前已经换不同的路径做过了),把 nodejs 和 yarn 都卸载重装,对了是不是跟 yarn 源还有关系?确认一下

$ yarn config get registry
https://registry.yarnpkg.com

果然这里不一样,忘了改成淘宝源,改一个再来一次(官方源为啥可能会有坑嘛,难道是最近撞墙撞的?不管了先死马当活马医好了)

$ yarn config set registry https://registry.npm.taobao.org
yarn config v1.16.0
success Set "registry" to "https://registry.npm.taobao.org".
Done in 0.06s.

这下应该没问题了,走你,等 yarn install 跑完,先检查下 node_modules/jsx-ast/utils/ 路径下文件对不对,嗯?还是只有 lib src 这两个文件夹?这到底是为什么啊,yarn 你这个坑货,要不换 npm 试试看?

npm install 跑一下,提示有文件权限不对,看了下,哦是之前有 sudo npm install -g 装过东西,把 ~/.npm 目录下相关内容清掉就好了。用 npm 装完一看,好了。。。呃,果然 yarn 你个坑货到底哪里不对啊!?

缓存?去吧 ~/.yarn 下的内容都清掉,/tmp 下有一堆 yarn- 开头的不知道是啥应该也都可以干掉,再来,还挂?去搜 yarn install package missing files,终于看到了一个很相似的情况,不过他这个最后也是清了 yarn 的 cache 就好了。看了下除了 ~/.yarn 还有人说 Cache 目录是 ~/.yarn-cache 目录,但是我压根都没这个目录啊。顺着这条线索继续搜,找到 yarn cache clean 这个命令,执行后再装就好了

那看来就是之前的缓存有问题,而我弄这么多各种重装也并没有清掉 cache,或者触发重新从远端拉包。那么 yarn 的 cache 到底在哪里?到用户根目录下查看所有文件,发现有一个目录叫 .cache,看着挺可疑,进去看看果然里面有 pipyarn 等目录,看了下里面文件时间,就是这了,应该就是之前某次安装过程中被强制退出,导致那个点在装的一个或几个包是不对的,但是目录还在 yarn 就一直傻乎乎的用本地的

结论:yarn 的 cache 机制对包的完整性保证不够,如果之前安装过程中有异常退出,赶上异常退出过程中的包就有可能损坏,后面如果不手动清 cache,就会一直挂

如何得到一个自适应宽度的高宽比固定的 HTML 容器

写上一篇文章时遇到了一个问题,我想插入一个 bilibili 的 iframe 来嵌入视频,如果直接用 B 站给的 iframe 代码,出来的是很小的一块,强行把 iframe 的 width 变成 100% 后,宽度是自适应撑开了,高度还是只有一点点高,然后 height 怎么写都不太对,如果再考虑手机等小屏幕设备需要自适应宽度,这个更无解了。搜了一圈,找到了一篇文章【前端笔记】使用iframe嵌入等比缩放的哔哩哔哩视频,按这个做法解决了问题

仔细看了下实现,其实挺有意思的,应该可以算前端的一个经典问题,就是如何设定一个 HTML 元素,能做到自适应宽度,且高宽比固定

一般我们需要维持高宽比的是图片,这个在 <img> 标签上可以有各种缩放选项能满足需求。对于视频,或者 iframe 嵌入,则没有比较好的原生实现方式。在上面找到的那个文章里,其实是先构建一个能自适应宽度且高宽比固定的 HTML 容器(一个 position: relative<div>),然后把真正要做到自适应的元素放到这个容器里,并用绝对定位占满空间,那么问题从怎么设定这个元素属性变成怎么得到可以自适应宽度且高宽比固定的一个容器

按搜到的那个实现做,是用一个空 <div>,先设定宽度 width: 100% 可以做到自适应宽度,接着为了维持高宽比,先强行设定高度 height: 0,然后用 padding-bottom: 75% 来做到等比撑开。这里的百分比计算是按照宽度来计算的,详细的定义可以看 w3.org 里关于 box 元素 padding-properties 的定义 http://www.w3.org/TR/2011/REC-CSS2-20110607/box.html#padding-properties。搜索结果里也都有这么来介绍,比如这两例:

今天问公司的前端担当 @tdzl2003,给出了另一个思路清奇的操作,直接塞一个比例图片进去,然后把这个图片撑开就行了,图片可以用透明色,而且 base64 编码后的图并没有多大,不会影响视觉效果也不影响性能,比如要 4:3 就弄一个 4 像素宽 3 像素高的空白图

果然前端都是各种骚操作,各种给跪

flask celery logging 翻车记

虽然前面自己写过一个 Python Logging 的各种玩法(折腾 Python logging 的一些记录),结果没两个月,自己就在之前特意叮嘱过的地方翻了车

事情是这样的,我们的项目使用 Flask 作为业务框架,用 Celery 作为异步任务框架,按上篇里提到的,我们加了个 Filter 来加入 request_id 用于跟踪同一个 Web 请求或同一个 Task。所以,我们的 logging.conf 一开始是这样的,在 logfile 这个 Handler 里加入 request_id 并打印到日志文件,我们用阿里云的 logtail 把所有部署实例的日志文件都收集到一起,这一层 app Logger 往上抛是让终端都打印所有的日志,并且用 sentry 这个 Handler 在最上面收集各种异常报错

logging_conf = {
    "version": 1,
    "disable_existing_loggers": True,
    "filters": {
        "addRequestId": {
            "()": "app.utils.logging.request_filter.ContextFilter"
        }
    },
    "formatters": {
        "standard": {
            "format": "[%(asctime)s][%(levelname)s][%(pathname)s:%(lineno)s][%(funcName)s]: %(message)s"
        },
        "withRequestId": {
            "format": "[%(asctime)s][%(levelname)s][%(relpath)s:%(lineno)s][%(funcName)s][%(request_id)s]: %(message)s"
        }
    },
    "handlers": {
        "console": {
            "level": "DEBUG",
            "class": "logging.StreamHandler",
            "formatter": "standard"
        },
        "logfile": {
            "level": "DEBUG",
            "class": "logging.handlers.WatchedFileHandler",
            "filters": ["addRequestId"],
            "formatter": "withRequestId",
            "filename": "log/app.log"
        },
        "sentry": {
            "level": "ERROR",
            "class": "raven.handlers.logging.SentryHandler",
            "dsn": "DSN_URI",
            "string_max_length": 512000
        }
    },
    "loggers": {
        "app": {
            "level": "DEBUG",
            "handlers": ["logfile"],
            "propagate": True
        }
    },
    "root": {
        "level": "DEBUG",
        "handlers": ["console", "sentry"]
    }
}

后来,为了把 Celery 调度信息等也通过 logtail 收集到阿里云,我们就把 logtail 的源从日志文件改成了 stdout/stderr 输出,不同时收集日志文件是因为同样的日志在 logfileconsole 里会出现两次,没有必要。但是只改 stdout/stderr 又会导致收集到的信息没有我们辛苦加进去的 relpathrequest_id,一个很直接的思路就是,在 app 这层 Logger 上加一个 consoleWithIdStreamHandler,并且 formatter 用 withRequestId 不就好了,然后限制 app Logger 的 propagate 为 False 禁止上抛,问题应该完美解决?等会,这里有好几个问题

第一个问题是,如果 app Logger 不往上抛,那万一异常了,sentry Handler 也收集不到错误?头疼医头脚疼医脚,那就给 app Logger 也挂上 sentry Handler 不就解决问题

第二个问题是,handlers 的处理顺序是不是严格按我们配置的顺序来?如果不是的话,consoleWithId 进入的时候,可能 addRequestId 这个 Filter 还没执行,出现了输出时拿不到 relpathrequest_id 那不就挂了?这个简单,把 Filter 移到 Logger 这一层不就解决了。至此,配置如下(只摘录改动部分)

    # ...
    "handlers": {
        # ...
        "logfile": {
            "level": "DEBUG",
            "class": "logging.handlers.WatchedFileHandler",
            "formatter": "withRequestId",
            "filename": "log/app.log"
        },
        "consoleWithId": {
            "level": "DEBUG",
            "class": "logging.StreamHandler",
            "formatter": "withRequestId"
        },
        # ...
    },
    "loggers": {
        "app": {
            "level": "DEBUG",
            "filters": ["addRequestId"],
            "handlers": ["logfile", "consoleWithId", "sentry"],
            "propagate": False
        }
    },
    # ...

翻车就翻在「这个简单」上,按这个思路配置后,跑起来还是在 consoleWithId 的 Handler 上输出报错,而且报的就是 relpathrequest_id 字段不存在。怀疑自己的配置有问题,跑到代码里打日志的地方用 logging.getLogger(__name__) 看拿到的到底是哪个 Logger,以及上面挂了哪些 Handler,还有 Logger 和 Handler 的 Filters 都配的啥,发现除了 app 根上,如果是 app.foo 这样的路径,拿到的都是一个叫 celery.utils.log.ProcessAwareLogger 的 Logger,而且没有任何 Handler 和 Filter 挂在上面,所以,Celery 你这个坏人,到底对我的代码做了什么?

跑去翻 celery.utils.log.ProcessAwareLogger 这个东西的源码都没看出个所以然,似乎只是为了保证 Flask 的 signal handler 机制正常,排查思路也断掉,再跑去看看我们那个 app.utils.logging.request_filter 的处理,有没有哪里不对的,在这个自定义的 filter 里裸用 print 打印,发现这个 filter 压根没被调用到?嗯?没被调用到?

回去看自己的上一篇,果然里面自己就提到过这里有坑(主流程解释的第 5 步)

如果开启了日志往上传递,则判断当前 Logger 是否有父 Logger,如果有的话,直接将当前 LogRecord 传给父 Logger 从 4 开始处理(跳过 1/2/3,注意此处级别控制 1 会不生效,绑定在父 Logger 上的 Filter 也不执行)

WTF!果然坑都是自己不掉一遍,别人说千万遍也不会记得的,哪怕说的这个人是自己。那好咯,把 addRequestId 这个 Filter 还是从 app Logger 上移到 Handler 层面上好了,每个需要的 Handler 都给挂上,多点性能开销就多点吧

不过这样配的感觉还是怪怪的,比如有些错误会被 sentry 收集两次,因为在 app 里一直往上抛会被 app Logger 里的 sentry 收集,如果这个错误还继续往上抛到了框架层面,框架的错误还会被 rootLogger 的 sentry 又收集一次。而且,既然 app 的里面和外面都有终端和 sentry,为啥不在最外面一次处理好,中间拦着不往上抛没有任何意义。调整了下,直接把 standard 这个 Formatter 和 console 这个 Handler 给去掉,在 rootLogger 上挂 consoleWithIdsentry 就好,最后完整的配置如下

logging_conf = {
    "version": 1,
    "disable_existing_loggers": True,
    "filters": {
        "addRequestId": {
            "()": "app.utils.logging.request_filter.ContextFilter"
        }
    },
    "formatters": {
        "request": {
            "format": "[%(asctime)s][%(levelname)s][%(relpath)s:%(lineno)s][%(funcName)s][%(request_id)s]: %(message)s"
        }
    },
    "handlers": {
        "logfile": {
            "level": "DEBUG",
            "class": "logging.handlers.WatchedFileHandler",
            "filters": ["addRequestId"],
            "formatter": "request",
            "filename": "log/app.log"
        },
        "consoleWithId": {
            "level": "DEBUG",
            "class": "logging.StreamHandler",
            "filters": ["addRequestId"],
            "formatter": "request"
        },
        "sentry": {
            "level": "ERROR",
            "class": "raven.handlers.logging.SentryHandler",
            "dsn": "DSN_URI",
            "string_max_length": 512000
        }
    },
    "loggers": {
        "app": {
            "level": "DEBUG",
            "handlers": ["logfile"],
        }
    },
    "root": {
        "level": "DEBUG",
        "handlers": ["consoleWithId", "sentry"]
    }
}

因为 Logger 的 propagate 默认就是 True,所以相对于第一版在 app 这个 Logger 上去掉了这条配置也没关系

最后,因为 addRequestId 这个 Filter 还是会被调两次,想优化下性能,就在 Filter 做完后加一个标记,下次再进来如果看到有这个标记就直接跳过,以及,对于非项目内的日志就不要用项目内的相对路径而用绝对路径替代。代码如下

# coding: utf8

import logging
import os.path

from celery import current_task
from flask import g, has_app_context, has_request_context


_proj_root_path = os.path.abspath(os.path.join(__file__, './../../../../'))
_proj_root_length = len(_proj_root_path)


class ContextFilter(logging.Filter):
    def filter(self, record):
        # ignore duplicate filter
        if hasattr(record, 'filter_by_yewen'):
            return True

        # request_id for flask web or celery task
        request_id = 'Standalone'

        if has_app_context():
            if has_request_context():
                request_id = g.get('request_id', 'UnknownRequest')
            elif current_task:
                request_id = current_task.request.id or 'UnknownTask'

        record.request_id = request_id

        # handle log_decorator pass
        record.funcName = getattr(record, 'orig_funcName', record.funcName)
        record.pathname = getattr(record, 'orig_pathname', record.pathname)
        record.lineno = getattr(record, 'orig_lineno', record.lineno)

        # relative path
        if record.pathname.startswith(_proj_root_path):
            record.relpath = record.pathname[_proj_root_length:]
        else:
            record.relpath = record.pathname

        record.filter_by_yewen = True

        return True