#62 logging 增加 TRACE 级别日志

2020-05-21

logging 内部的服务级别:

DEBUG       10
INFO        20
WARNING     30
ERROR       40
CRITICAL    50

根据使用习惯,INFO 是重要信息,DEBUG 是普通信息。线上也是开到 DEBUG 级别。
然后调试信息也是通过 DEBUG 服务打印,然后通过 conf.DEBUG_MODE 来区分是不是要打印这种 DEBUG 级别的调试信息。

觉得不甚方便,想了一下,有两种思路:

  1. 将普通信息也通过 INFO 日志打印,在日志内容中插入部分标识来表示是重要信息,比如 “&NOTICE”。
  2. 增加一个 TRACE 级别日志。

方案一感觉相对合理一些,但是对于已有项目还是方案二好。

实现

def trace(self, message, *args, **kwargs):
    if self.isEnabledFor(TRACE):
        self._log(TRACE, message, args, **kwargs)


TRACE = logging.TRACE = 5
logging.addLevelName(TRACE, 'TRACE')
logging.Logger.trace = trace

参考:syslog 日志级别

  • EMERG:系统不可用
  • ALERT:需要立即采取行动
  • CRIT:关键错误
  • ERR:一般错误
  • WARNING:警告
  • NOTICE:一般通知
  • INFO:信息性消息
  • DEBUG:调试级别的消息

参考:nodejs winston 日志级别

  • error:错误
  • warn:警告
  • info:一般信息
  • http:HTTP 请求
  • verbose:详细信息
  • debug:调试信息
  • silly:非常详细的调试信息

参考:java log4j 日志级别

  • FATAL:致命
  • ERROR:错误
  • WARN:警告
  • INFO:信息
  • DEBUG:调试
  • TRACE:跟踪

#61 PyCryptodome

2020-02-05

和 PyCrypto 的关系

PyCrypto 是 Python 界最知名的加密模块,它提供了一系列的加密算法,包括对称加密、非对称加密、哈希算法、签名算法等。
不过有一个很大的问题:上一个版本 2.6.1 发布于 2013-10-18,已经很多年没有维护了。

PyCryptodome 是 PyCrypto 的分叉,该项目在统一套代码的基础上提供了两种包:pycryptodome 和 pycryptodomex:

  1. 前者保持对 PyCrypto 的兼容,所有的代码都在 Crypto 名称下,
  2. 后者丢掉了历史包袱,放弃对 PyCrypto 的兼容,所有代码都在 Cryptodome 名称下。

  3. https://pypi.org/project/pycryptodome/

  4. https://pypi.org/project/pycryptodomex/
  5. https://www.pycryptodome.org/en/latest/
  6. https://github.com/Legrandin/pycryptodome/

其他加密模块

示例


#60 Python 打开文件的方式

2020-01-10

有同事排查 Python 项目问题的时候指出一处 open 没有关闭可能会导致句柄泄露 Handle Leak。
PS: 句柄泄漏的危害:大量资源占用可能导致性能下降,甚至由于可打开文件数达到极限,服务无法继续向外提供服务。

我看了之后告诉他,此处函数退出之后句柄会自动关闭,他还不信,下去自己研究了一会儿,可能是百度一下,过一会儿说好像确实是这样,不过他仍然很疑惑,那么 with open 的作用是什么呢?

一般我们常用上下文管理的方式(with open)来打开文件,这样可以自动关闭句柄,这是一个好的实践。

  1. 退出函数之后文件描述符的自动关闭是 CPython GC (垃圾回收机制) 的特性,非 Python 语言规范。
  2. 根据 CPython 的 GC 策略(引用计数),如果有引用,文件描述符不会被关闭,这是一个非常严重的潜在风险。
    大部分时候我们可能没有引用文件描述符,但是不能排除可能性。
    万一出现句柄泄漏,在代码库中排查可能的未关闭引用会比较麻烦。
  3. 上下文管理会自动处理异常,相当于我们的 close 方法放在 finally 块中。

无关的事情:进程退出时的句柄

进程退出时如果有没有关闭的句柄,

  1. 如果程序正常退出,
  2. 语言可能会处理一道,清理相关数据
  3. 系统会处理一道
  4. 如果程序异常退出,则只有靠系统了

至少我看到 POSIX 中有相关规定,无论任何原因或任何方式的退出,都应该:

All of the file descriptors, directory streams, conversion descriptors, and message catalog descriptors open in the calling process shall be closed.

PS: 其中提到的:

  1. 文件描述符 File Descriptors
  2. 目录流 Directory Streams
  3. 转换描述符 Conversion Descriptors
  4. 消息编码描述符 Message Catalog Descriptors

后面三个是个啥?

#59 Python DataClass

2020-01-02

Python 3.7 新增了 dataclass,主要的好处是提供了一个类型定义模板,自动生成常见方法,减少一些常用的重复工作(__init__、__repr__、__eq__、__hash__ 和 __lt__ 等方法)。

示例

import dataclasses

@dataclasses.dataclass
class User:
    user_id: int
    user_name: str
    first_name: str
    last_name: str
    age: int = 0

    def __post_init__(self):
        self.full_name = f'{self.first_name} {self.last_name}'

u = User(1, 'admin', 'Jim', 'Green')
print(u)
# User(user_id=1, user_name='admin', first_name='Jim', last_name='Green', age=0)
print(u.full_name)
# Jim Green
u.full_name = 'Han Meimei'
print(u.full_name)
# Han Meimei
print(dataclasses.asdict(u))
# {'user_id': 1, 'user_name': 'admin', 'first_name': 'Jim', 'last_name': 'Green', 'age': 0}
print(dataclasses.astuple(u))
# (1, 'admin', 'Jim', 'Green', 0)

相当于:

class User:
    def __init__(self, user_id: int, user_name: str, first_name: str, last_name: str, age: int = 0):
        self.user_id = user_id
        self.user_name = user_name
        self.first_name = first_name
        self.last_name = last_name
        self.full_name = f'{self.first_name} {self.last_name}'
        self.age = age

    def __str__(self):
        return f"{self.__class__.__name__}(user_id={self.user_id}, user_name='{self.user_name}', first_name='{self.first_name}', last_name='{self.last_name}, age={self.age}')"

    def asdict(self):
        return {
            'user_id': self.user_id,
            'user_name': self.user_name,
            'first_name': self.first_name,
            'last_name': self.last_name,
            'age': self.age,
        }

    def astuple(self):
        return self.user_id, self.user_name, self.first_name, self.last_name, self.age,

u = User(1, 'admin', 'Jim', 'Green')
print(u)
print(u.full_name)
u.full_name = 'Han Meimei'
print(u.full_name)
print(u.asdict())
print(u.astuple())

其他特性

@dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False,
    unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False,
    weakref_slot=False)
  • init 是否生成 __init__ 方法(默认)
  • repr 是否生成 __repr__ 方法(默认)
  • eq 是否生成 __eq__ 方法(默认)
  • order 是否生成 __lt__,__le__,__gt__,__ge__ 方法
  • unsafe_hash 是否生成 __hash__ 方法
    能 hash 的是不会变化对象,否则会出问题,需要谨慎使用。
  • frozen 是否允许对字段赋值
    可以使得数据类的实例变成不可变对象,类似于 Python 的 namedtuple。这意味着一旦实例化后,无法修改其中的属性值。
  • match_args
  • kw_only 是否仅关键词传参
  • slots 是否生成 __slots__ 方法
  • weakref_slot 是否添加 __wrakref__ 槽位(弱引用)
    指定 weakref_slot=True 而不同时指定 slots=True 将会导致错误。

dataclass 和 __slots__ 的区别

dataclass 作为代码模板,作用是简化类的定义
__slots__ 的作用是优化内存使用(dataclass 可以通过 slots=True 自动生成 __slots__ 定义)

from dataclasses import dataclass

# 使用 dataclass
@dataclass
class Person:
    name: str
    age: int
    email: str = None

# 创建数据类实例
p = Person(name="Alice", age=30)
print(p)  # Person(name='Alice', age=30, email=None)

# 使用 __slots__
class PersonWithSlots:
    __slots__ = ['name', 'age', 'email']

    def __init__(self, name, age, email=None):
        self.name = name
        self.age = age
        self.email = email

# 创建带 __slots__ 的实例
p2 = PersonWithSlots(name="Alice", age=30)
print(p2.name, p2.age)  # Alice 30

#58 Python sys.audit

2019-11-06

在学习 subprocess 模块的时候看到代码中有一行:

sys.audit("subprocess.Popen", executable, args, cwd, env)

才了解到 Python 3.8 中新加了这样一个审计功能。

作用

提供一种机制,对 Python 程序执行过程中的部分关键操作进行干预,比如记录事件、引发异常、中止操作或完全终止进程。

部分关键操作发生的时候,Python 会调用我们定义的钩子方法。

注意:官方提示,恶意代码可以轻易绕过这些钩子,不要指望审计钩子能用来创建一个安全的 sandbox。
注意:sys.addaudithook 方法的调用会触发一个同名的事件。如果现有的钩子方法抛出 Runtime 异常,则不会添加这个钩子。

支持的审计事件

CPython 定义了一些事件:Audit events table,比如:

  • import
  • exec
  • compile
  • os.exec
  • os.listdir
  • os.rmdir
  • os.spawn
  • os.walk
  • os.system
  • subprocess.Popen

使用

sys.audit 方法是官方定义和执行的,Python 运行时负责抛出审计事件,我们程序只管使用系统方法 sys.addaudithook(hook) 来添加审计钩子处理这些事件。

import sys

def hook(event, args):
    print('%s: %r' % (event, args))

sys.addaudithook(hook)

import math
num = math.ceil(100 / 3)
print(num)

#56 Django 3.0 发布,开始支持异步功能

2019-09-16

Django 项目组试图在保持向后兼容的基础之上,对阻塞部分进行改造,使之支持异步(通过装饰器的方式)。
包括 Session、Auth、ORM 与 Handlers 等。

ASGI 模式将 Django 作为原生异步应用程序运行,原有的 WSGI 模式将围绕每个 Django 调用运行单个事件循环,以使异步处理层与同步服务器兼容。

在这个改造的过程中,每个特性都会经历以下三个实现阶段:

  • Sync-only,只支持同步,也就是当前的情况
  • Sync-native,原生同步,同时带有异步封装器
  • Async-native,原生异步,同时带同步封装器

Django 3.0 开始提供运行 ASGI 应用支持,让 Django 逐渐具备异步功能。做了这一改动后,Django 现在会感知到异步事件循环,并将阻止从异步上下文调用标记为 “异步不安全” 的代码(例如 ORM 操作),如果开发者之前使用的是异步代码,则可能会触发。如果看到 SynchronousOnlyOperation 错误,可以仔细检查代码并将数据库操作移到同步子线程中。

其它方面,Django 现在支持 MariaDB 10.1 及更高版本;新的 ExclusionConstraint 类可以在 PostgreSQL 上添加排除约束;输出 BooleanField 的表达式现在可以直接在 QuerySet 过滤器中使用,而无需先注解然后对注解进行过滤;自定义枚举类型 TextChoices、IntegerChoices 和 Choices 现在可用作定义 Field.choices 的方法。

需要特别注意的是:自从 2.2 之后,Django 将不再支持 Python 3.5。

更新说明:https://docs.djangoproject.com/en/dev/releases/3.0