Python 中可观测性的 7 个关键部分
学习为什么 Python 中的可观测性很重要，以及如何在你的软件开发生命周期中实现它。
Python 可观测性
学习为什么 Python 中的可观测性很重要，以及如何在你的软件开发生命周期中实现它。

> 
> 学习为什么 Python 中的可观测性很重要，以及如何在你的软件开发生命周期中实现它。
> 
> 
> 


![](/data/attachment/album/202208/02/115713cbml51nooltb21bx.jpg "Searching for code")


你写的应用会执行很多代码，而且是以一种基本上看不到的方式执行。所以你是怎么知道：


* 代码是否在运行？
* 是不是在正常工作？
* 谁在使用它，如何使用？


可观测性是一种能力，可以通过查看数据来告诉你，你的代码在做什么。在这篇文章中，主要关注的问题是分布式系统中的服务器代码。并不是说客户端应用代码的可观测性不重要，只是说客户端往往不是用 Python 写的。也不是说可观测性对数据科学不重要，而是在数据科学领域的可观测性工具（大多是 Juptyter 和快速反馈）是不同的。


### 为什么可观测性很重要


所以，为什么可观测性重要呢？在软件开发生命周期（SDLC）中，可观测性是一个关键的部分。


交付一个应用不是结束，这只是一个新周期的开始。在这个周期中，第一个阶段是确认这个新版本运行正常。否则的话，很有可能需要回滚。哪些功能正常运行？哪些功能有细微的错误？你需要知道发生了什么，才能知道接下来要怎么做。这些东西有时候会以奇怪的方式不能正常运行。不管是天灾，还是底层基础设施的问题，或者应用进入了一种奇怪的状态，这些东西可能在任何时间以任何理由停止工作。


在标准 SDLC 之外，你需要知道一切都在运行中。如果没有，有办法知道是怎么不能运行的，这是非常关键的。


### 反馈


可观测性的第一部分是获得反馈。当代码给出它正在做什么的信息时，反馈可以在很多方面提供帮助。在模拟环境或测试环境中，反馈有助于发现问题，更重要的是，以更快的方式对它们进行分类。这可以改善在验证步骤中的工具和交流。


当进行<ruby> 金丝雀部署 <rt>  canary deployment </rt></ruby>或更改特性标志时，你需要知道是否要继续，还是等更长时间，或者回滚，反馈就显得很重要了。


### 监控


有时候你怀疑有些东西不太对。也许是一个依赖服务有问题，或者是社交网站爆出了大量你的网站的问题。也许在相关的系统中有复杂的操作，然后你想确保你的系统能完美处理。在这些情况下，你就想把可观测性系统的数据整合到控制面板上。


当写一个应用的时候，这些控制面板需要是设计标准的一部分。只有当你的应用能把数据共享给这些控制面板，它们才会把这些数据显示出来。


### 警报


看控制面板超过 15 分钟就像看着油漆变干一样。任何人都不应该遭受这种折磨。对于这种任务，我们要有报警系统。报警系统将可观测性数据与预期数据进行对比，当它们不匹配的时候就发出通知。完全深入研究时间管理超出了本文的范围。然而，从两方面来说，可观测应用是<ruby> 报警友好的 <rt>  alert-friendly </rt></ruby>：


* 它们有足够多，足够好的数据，发出的警报才是高质量的。
* 警报有足够的数据，或者接收者可以很容易的得到数据，这样有助于找到源头。


高质量警报有三个特点：


* 较少的错报：如果有警报，那一定是有问题了。
* 较少的漏报：如果有问题，那一定有警报触发。
* 及时性：警报会迅速发出以减少恢复时间。


这三个特点是互相有冲突的。你可以通过提高监测的标准来减少错误警报，代价是增加了漏报。你也可以通过降低监测的门槛来减少漏报，代价是增加错报。通过收集更多数据，你也可以同时减少错报和漏报，而代价是降低了及时性。


同时改善这三个参数就更难了。这就要求高质量的可观测性数据。更高质量的数据可以同时改善这三个特点。


### 日志


有的人喜欢嘲笑用打印来调试的方法。但是，在一个大多数软件都不在你本机运行的世界里，你所能做的只有打印调试。日志记录就是打印调试的一种形式。尽管它有很多缺点，但 Python 日志库提供了标准化的日志记录。更重要的是，它意味着你可以通过这些库去记录日志。


应用程序要负责配置日志的记录方式。讽刺地是，在应用程序对配置日志负责了多年以后，现在越来越不是这样了。在现代容器<ruby> 编排 <rt>  orchestration </rt> <ruby>  环境中，现代应用程序记录标准错误和标准输出，并且信任  <ruby>   编排   <rt>    orchestration   </rt>   <ruby>    系统可以合理的处理日志。   </ruby>  </ruby> </ruby></ruby>


然而，你不应该依赖库，或者说，其他任何地方。如果你想让操作的人知道发生了什么，*使用日志，而不是打印*。


#### 日志级别


日志记录的一个最重要功能就是 *日志级别*。不同的日志级别可以让你合理的过滤并分流日志。但是这只有在日志级别保持一致的情况下才能做到。最后，你应该在整个应用程序中保持日志级别的一致性。


选择不兼容语义的库可以通过在应用层面的适当配置来追溯修复，这只需要通过使用 Python 中最重要的通用风格做到：`getLogger(__name-_)`。


大多数合理的库都会遵循这个约定。<ruby> 过滤器 <rt>  Filters </rt></ruby>可以在日志对象发出之前就地修改它们。你可以给处理程序附加一个过滤器，这个处理程序会根据名称修改消息，使其具有合适的级别。



```
import logging
LOGGER=logging.getLogger(__name__)

```

考虑到这一点，你现在必须明确日志级别的语义。这其中有很多选项，但是下面这些是我的最爱：


* `Error`：发送一个即时警告。应用程序处于一个需要操作人员引起注意的状态。（这意味着包含 `Critical` 和 `Error`）
* `Warning`：我喜欢把这些称作“工作时间警报”。这种情况下，应该有人在一个工作日内关注一下。
* `Info`：这是在正常工作流程中发出的。如果怀疑有问题的时候，这个是用来帮助人们了解应用程序在做什么的。
* `Debug`：默认情况下，这个不应该在生产环境中出现。在模拟环境或开发环境下，可以发出来，也可以不发。如果需要更多的信息，在生产环境也可以特地被打开。


任何情况下都不要在日志中包含<ruby> 个人身份信息 <rt>  Personal Identifiable Information </rt></ruby>（PII）或密码。无论日志级别是什么，都是如此，比如级别更改，激活调试级别等等。日志聚合系统很少是 <ruby> PII 安全 <rt>  PII-safe </rt></ruby>的，特别是随着 PII 法规的不断发展（HIPAA、GDPR 等等）。


#### 日志聚合


现代系统几乎都是分布式的。<ruby> 冗余 <rt>  redundancy </rt> <ruby>  、  <ruby>   扩展性   <rt>    scaling   </rt>  </ruby>  ，有时是  <ruby>   管辖权   <rt>    jurisdictional   </rt>  </ruby>  需要更多的水平分布。微服务意味着垂直分布。登录到每个机器去查看日志已经是不现实的了。出于合理的控制原因，允许开发人员登录到机器中会给予他们更多的权限，这不是个好主意。 </ruby></ruby>


所有的日志都应该被发到一个聚合器。有一些商业的方案，你可以配置一个 ELK 栈，或者也可以使用其他的数据库（SQL 或则 no-SQL）。作为一个真正的低技术解决方案，你可以将日志写入文件，然后将它们发送到对象存储中。有很多解决方案，但是最重要的事情是选择一个，并且将所有东西聚合到一起。


#### 记录查询


在将所有东西记录到一个地方后，会有很多日志。具体的聚合器可以定义如何写查询，但是无论是通过从存储中搜索还是写 NoSQL 查询，记录查询以匹配源和细节都是很有用的。


### 指标抓取


<ruby> 指标抓取 <rt>  Metric Scraping </rt> <ruby>  是一个  <ruby>   服务器拉取   <rt>    server pull   </rt>   <ruby>    模型。指标服务器定时和应用程序连接，并且拉取指标。   </ruby>  </ruby> </ruby></ruby>


最后，这意味着服务器需要连接和找到所有相关的应用服务器。


#### 以 Prometheus 为标准


如果你的指标聚合器是 Prometheus，那么 [Prometheus](https://opensource.com/article/21/7/run-prometheus-home-container) 格式做为一个<ruby> 端点 <rt>  endpoint </rt></ruby>是很有用的。但是，即使聚合器不是 Prometheus，也是很有用的。几乎所有的系统都包含与 Prometheus 端点兼容的<ruby> 垫片 <rt>  shim </rt></ruby>


使用客户端 Python 库给你的应用程序加一个 Prometheus 垫片，这将使它能够被大多数的指标聚合器所抓取。当 Prometheus 发现一个服务器，它就期望找到一个指标端点。这经常是应用程序路由的一部分，通常在 `/metrics` 路径下。不管 Web 应用的平台是什么，如果你能在一个端点下运行一个定制类型的定制字节流，Prometheus 就可以将它抓取。


对于大多数流行的框架，总有一个中间件插件或者类似的东西收集指标，如延迟和错误率。通常这还不够。你需要收集定制的应用数据：比如，每个端点的缓存<ruby> 命中/缺失 <rt>  hit/miss </rt></ruby>率，数据库延迟，等等。


#### 使用计数器


Prometheus 支持多个数据类型。一个重要且巧妙的类型就是计数器。计数器总是在前进 —— 但有一点需要注意。


当应用重置，计数器会归零。计数器中的这些“<ruby> 历时 <rt>  epochs </rt></ruby>”通过将计数器“创建时间”作为元数据发送来管理。Prometheus 知道不去比较两个不同<ruby> 历时 <rt>  epochs </rt></ruby>的计数器。


#### 使用仪表值


仪表值会简单很多：它们测量瞬时值。用它们来测量会上下起伏的数据：比如，分配的总内存大小，缓存大小，等等。


#### 使用枚举值


枚举值对于整个应用程序的状态是很有用的，尽管它们可以以更精细的方式被收集。比如，你正使用一个<ruby> 功能门控 <rt>  feature-gating </rt></ruby>框架，一个有多个状态（比如，使用中、关闭、<ruby> 屏蔽 <rt>  shadowing </rt></ruby> 等）的功能，也许使用枚举会更有用。


### 分析


分析不同于指标，因为它们要对应连续的事件。比如，在网络服务器中，事件是一个外部请求及其产生的工作。特别是，在事件完成之前事件分析是不能被发送的。


事件包含特定的指标：延迟，数量，以及可能产生的对其他服务请求的细节，等等。


#### 结构化日志


现在一个可能的选择是将日志结构化。发送事件只发送带有正确格式的有效<ruby> 载荷 <rt>  payload </rt></ruby>的日志。这个数据可以从日志聚合器请求，然后解析，并且放入一个合适的系统，这样可以对它的可见性。


### 错误追踪


你可以使用日志来追踪错误，也可以用分析来追踪错误。但是一个专门的错误系统还是值得的。一个为错误而优化的系统可以发送更多的错误，因为错误毕竟还是罕见的。这样它就可以发送正确的数据，并且用这些数据，它能做更多智能的事情。Python 中的错误追踪系统通常和一般的异常处理关联，然后收集数据，并且把它发到一个专门的错误聚合器。


#### 使用 Sentry


很多情况下，自己运行 Sentry 是正确的做法。当错误发生时，就说明有些东西就出问题了。可靠地删除敏感数据是不可能的，因为一定有会出现敏感数据被发送到不应该的地方。


通常，这种工作量并不会很大：异常并不常出现。最后，这个系统并不需要很高的质量，也不需要高可靠性的备份。昨天的错误应该已经修复了，希望如此，如果没有，你还会发现的！


### 快速、安全、可重复：三者都要


可观测的系统开发起来更快，因为它们可以给你提供反馈。它们运行起来也更安全，因为当出问题的时候，它们也会更早的让你知道。最后，因为有反馈回路，可观测性也有助于围绕它构建可重复的过程。可观测性可以让你了解你的应用程序。而更了解它们，就胜利了一半。


#### 磨刀不误砍柴功


构建所有的可观测层是一件困难的事情。总会让人感觉是在浪费的工作，或者更像是“可以有，但是不急”。


之后再做这个可以吗？也许吧，但是不应该。正确的构建可观测性可以加速后面所有阶段的开发：测试、监控，甚至是培训新人。在一个和科技行业一样动荡的行业，减少培训新人的工作量绝对是值得的。


事实上，可观测性很重要，所以尽早把它写出来，然后就可以在整个过程中进行维护。反过来，它也会帮你维护你的软件。




---


via: <https://opensource.com/article/21/11/observability-python>


作者：[Moshe Zadka](https://opensource.com/users/moshez) 选题：[lujun9972](https://github.com/lujun9972) 译者：[MCGA](https://github.com/Yufei-Yan) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出

