目录

1.引言

2.logging:可观测性的“黑匣子”

3.math:浮点世界里的“数学瑞士军刀”

4.logging + math:一个端到端示例

5.结语


1.引言

Python 标准库里最容易被低估的两个模块,一个是 logging,一个是 math。
前者是“可观测性”世界的基石,后者是“数值计算”世界的起点。
把二者放在一起,看似风马牛不相及:一个负责把程序行为说出来,一个负责把数学运算算出来。但任何稍具规模的系统,都离不开“算”与“说”的双重保障:算法必须算得准,系统必须说得清。
本文试图用 3000 字左右的篇幅,从设计理念、核心 API、配置细节、性能陷阱、生态整合到实战案例,把这两个模块讲透。读完之后,你不仅能写出优雅高效的日志,也能在浮点世界游刃有余。 


2.logging:可观测性的“黑匣子”

2.1 设计哲学:事件驱动与分级
logging 模块把“程序运行时产生的一切值得记录的信息”抽象为 LogRecord 事件。事件有八大级别(NOTSET、DEBUG、INFO、WARNING、ERROR、CRITICAL),外加一个介于 DEBUG 与 INFO 之间的 TRACE(logging 本身未定义,但社区常用)。
分级不是为了“过滤噪音”,而是为了“在不同维度上消费事件”。DEBUG 面向开发者,INFO 面向运维,WARNING 面向告警,ERROR/CRITICAL 面向应急。

2.2 从 print 到 logging:一次进化的必然
print 是“面向控制台”的瞬态输出;logging 是“面向系统”的持久化事件流。
print 无法区分级别、无法批量重定向、无法按时间轮转、无法与第三方框架集成。logging 全部解决。

2.3 核心 API 速览
import logging
logger = logging.getLogger(name) # 推荐“模块级”命名
logger.setLevel(logging.DEBUG) # 控制本 logger 的门禁
logger.debug("pi=%s", math.pi) # 延迟格式化,防字符串构造开销

2.4 Handler、Formatter、Filter:三驾马车
Handler:把日志写到哪里。StreamHandler、FileHandler、RotatingFileHandler、TimedRotatingFileHandler、SocketHandler、HTTPHandler、SysLogHandler、NullHandler……
Formatter:把 LogRecord 格式化成字符串。经典格式:"[%(asctime)s] %(levelname)s %(name)s: %(message)s"。
Filter:比级别更灵活的过滤。可基于 logger 名、record 属性、甚至自定义函数。

2.5 配置方式:basicConfig、dictConfig、fileConfig
basicConfig:10 行代码内搞定 80% 需求。
dictConfig:Python 代码即配置,支持热更新。
fileConfig:INI 风格,适合运维同学。

2.6 结构化日志与 JSONFormatter
from pythonjsonlogger import jsonlogger
formatter = jsonlogger.JsonFormatter()
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
{"message": "pi estimation started", "n_samples": 1000000, "level": "INFO", "timestamp": "2025-07-29T11:42:00Z"}

2.7 性能考量与最佳实践

  • 不要在热路径里创建 logger:模块顶层实例化即可。

  • 用 %s 延迟格式化,而不是 f-string。

  • 设置 propagate=False 防止重复打印。

  • 在协程里用 contextvars 传递 request_id。

2.8 线程、协程、多进程中的日志隔离

  • 线程:logging 是线程安全的,无需锁。

  • 协程:用 asyncio.Queue + QueueHandler 实现异步日志。

  • 多进程:multiprocessing.Queue + QueueListener,或直接用 concurrent-log-handler。

2.9 与主流框架的集成
Django:LOGGING = {...}
FastAPI:依赖注入 Request 时拿到 request_id,放进 logging.Filter。
Celery:worker_hijack_root_logger=False,再自定义 logger。

2.10 小结
logging 的精髓在于“把日志当事件”,而非“把日志当 print”。理解 Logger/Handler/Formatter/Filter 的分层模型,就能在任意规模系统里优雅地记录一切。


3.math:浮点世界里的“数学瑞士军刀”

3.1 设计哲学:IEEE-754、精度与可移植性
math 模块是 CPython 对 C 标准库 <math.h> 的薄封装,因此行为与平台数学库严格一致;所有 float 都遵循 IEEE-754 双精度(53 位尾数,11 位指数,1 位符号)。

3.2 常量与基本函数
math.pi、math.e、math.tau、math.inf、math.nan。
isfinite、isinf、isnan、copysign、fabs、fsum。

3.3 整数与浮点
ceil(x) 与 floor(x) 返回 int;modf(x) 返回 (小数部分, 整数部分) 的 float 元组。
fsum([0.1]*10) 给出 1.0,而 sum([0.1]*10) 得 0.9999999999999999。

3.4 幂、对数与三角
log(x[, base])、log1p(x)(避免 x≈0 精度丢失)、pow(x, y)、sqrt、hypot(*coordinates)。
sin、cos、tan 及它们的反函数,角度与弧度转换:degrees、radians。

3.5 特殊函数
gamma、lgamma、erf、erfc、comb(n, k)、perm(n, k=None)。
注意:comb 返回 int,perm 返回 int;当 n 大时可能溢出,需要 math.comb 的溢出检查或 gmpy2。

3.6 复数外的“剩余领土”
cmath 模块接管复数域;math 只处理实数。

3.7 性能剖析
C 级实现,单核性能已接近 C。与 NumPy 的区别:math 是 scalar,NumPy 是 vector。
用 timeit:python -m timeit -s "import math" "math.sin(1.0)" 可得 80 ns/次。

3.8 常见陷阱

  • 0.1 + 0.2 != 0.3:用 math.isclose(a, b, rel_tol=1e-9)。

  • 溢出:math.exp(1000) 会 raise OverflowError。

  • 比较 nan:nan != nan 为 True,用 math.isnan。

3.9 与 decimal、fractions、statistics 的协同
高精度财务 → decimal;有理数符号计算 → fractions;
描述统计 → statistics。
示例:
from decimal import Decimal, getcontext
getcontext().prec = 50
Decimal(math.pi) # 仅 15 位有效数字,别这么干!

3.10 小结
math 是浮点世界的“确定性锚点”。牢记精度、舍入、溢出三大陷阱,善用 isclose、fsum、log1p 等高保真函数,就能在数值计算中保持稳健。


4.logging + math:一个端到端示例

本节用 100 行不到的代码实现一个“实时蒙特卡洛 π 估计器”,同时演示 logging 与 math 的协同。 

4.1 需求

  • 每秒采样 100 万次,持续 1 分钟。

  • 每 5 秒打印一次 π 估计值及 95% 置信区间。

  • 日志结构化输出,可对接 ELK。

4.2 设计

  • 采样:random.random() 生成 (x, y) ∈ [0,1]²,判定点是否在单位圆内。

  • 统计:math.sqrt、math.isclose、math.comb 无用,但 math.log 用来计算置信区间。

  • 日志:使用 QueueHandler + QueueListener 实现异步日志,避免采样线程阻塞。

4.3 代码

import logging, logging.config, math, random, time, threading, json, queue
from datetime import datetime

# ---------- 1. 日志配置 ----------
LOG_Q = queue.Queue(-1)
conf = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "json": {
            "()": "pythonjsonlogger.jsonlogger.JsonFormatter",
            "format": "%(asctime)s %(levelname)s %(message)s"
        }
    },
    "handlers": {
        "queue": {"class": "logging.handlers.QueueHandler", "queue": LOG_Q},
        "console": {"class": "logging.StreamHandler", "formatter": "json"}
    },
    "loggers": {"monte": {"level": "INFO", "handlers": ["queue"]}},
    "root": {"level": "INFO", "handlers": ["queue"]}
}
logging.config.dictConfig(conf)
logger = logging.getLogger("monte")

# ---------- 2. 启动 QueueListener ----------
listener = logging.handlers.QueueListener(LOG_Q, logging.StreamHandler())
listener.start()

# ---------- 3. 采样线程 ----------
samples_total = 0
hits_total = 0
lock = threading.Lock()

def sampler():
    global samples_total, hits_total
    rng = random.Random()   # 线程独立 RNG
    while True:
        x, y = rng.random(), rng.random()
        with lock:
            samples_total += 1
            if math.sqrt(x*x + y*y) <= 1.0:
                hits_total += 1

threading.Thread(target=sampler, daemon=True).start()

# ---------- 4. 主循环 ----------
start = time.time()
while time.time() - start < 60:
    time.sleep(5)
    with lock:
        n = samples_total
        k = hits_total
    p_hat = k / n
    pi_est = 4 * p_hat
    # 95% Wilson 置信区间
    z = 1.96
    denom = 1 + z*z/n
    centre = (p_hat + z*z/(2*n)) / denom
    delta = z * math.sqrt(p_hat*(1-p_hat)/n + z*z/(4*n*n)) / denom
    lo, hi = 4*(centre - delta), 4*(centre + delta)
    logger.info("snapshot", extra={
        "n": n, "pi_est": pi_est,
        "ci95": [lo, hi], "elapsed": round(time.time() - start, 1)
    })

listener.stop()

4.4 运行与观察
$ python monte.py | jq
{"asctime": "2025-07-29 11:45:05,123", "levelname": "INFO", "message": "snapshot", "n": 500000, "pi_est": 3.141216, "ci95": [3.1368, 3.1456], "elapsed": 5.0}

把输出重定向到 Filebeat → Logstash → Elasticsearch → Kibana,即可看到实时 π 估计的置信区间收敛曲线。


5.结语

logging 让系统“说话”,math 让系统“算准”。
在 3000 字的篇幅里,我们走完了两条看似平行却殊途同归的道路:一条通向可观测性,一条通向数值确定性。 

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐