Moshe Zadka 发布的文章

Rust 和 Python 的优势互补。可以使用 Python 进行原型设计,然后将性能瓶颈转移到 Rust 上。

Python 和 Rust 是非常不同的语言,但它们实际上非常搭配。但在讨论如何将 Python 与 Rust 结合之前,我想先介绍一下 Rust 本身。你可能已经听说了这种语言,但可能还没有了解过它的细节。

什么是 Rust?

Rust 是一种低级语言,这意味着程序员所处理的东西接近于计算机的 “真实” 运行方式。

例如,整数类型由字节大小定义,与 CPU 支持的类型相对应。虽然我们很想简单地说 Rust 中的 a+b 对应于一条机器指令,但实际上并不完全是这样!

Rust 编译器链非常复杂。作为第一种近似的方法,将这样的语句视为 “有点” 真实是有用的。

Rust 旨在实现零成本抽象,这意味着许多语言级别可用的抽象在运行时环境中会被编译去掉。

例如,除非明确要求,对象会在堆栈上分配。结果是,在 Rust 中创建本地对象没有运行时成本(尽管可能需要进行初始化)。

最后,Rust 是一种内存安全的语言。也有其他内存安全的语言和其他支持零成本抽象的语言。但通常这些是两类不同的语言。

内存安全并不意味着不可能在 Rust 中出现内存违规。它确实意味着只有两种方式可能导致内存违规:

  • 编译器的错误。
  • 显式声明为不安全(unsafe)的代码。

Rust 标准库代码有很多被标记为不安全的代码,虽然比许多人预期的少。这并不意味着该语句无意义。除了需要自己编写不安全代码的(罕见的)情况外,内存违规通常是由基础设施造成的。

为什么会有 Rust 出现?

为什么人们要创建 Rust?是哪些问题没有被现有编程语言解决吗?

Rust 被设计成既能高效运行,又保证内存安全。在现代的联网世界中,这是一个越来越重要的问题。

Rust 的典型应用场景是协议的低级解析。待解析的数据通常来自不受信任的来源,并且需要通过高效的方式进行解析。

如果你认为这听起来像 Web 浏览器所做的事情,那不是巧合。Rust 最初起源于 Mozilla 基金会,它是为了改进 Firefox 浏览器而设计的。

如今,需要保证安全和速度的不仅仅是浏览器。即使是常见的微服务架构也必须能够快速解析不受信任的数据,同时保证安全。

现实示例:统计字符

为了理解 “封装 Rust” 的例子,需要解决一个问题。这个问题需要满足以下要求:

  • 足够容易解决。
  • 能够写高性能循环来优化。
  • 有一定的现实意义。

这个玩具问题的例子是判断一个字符在一个字符串中是否出现超过了 X 次。这个问题不容易通过高效的正则表达式解决。即使是专门的 Numpy 代码也可能不够快,因为通常没有必要扫描整个字符串。

你可以想象一些 Python 库和技巧的组合来解决这个问题。然而,如果在低级别的语言中实现直接的算法,它会非常快,并且更易于阅读。

为了使问题稍微有趣一些,以演示 Rust 的一些有趣部分,这个问题增加了一些变化。该算法支持在换行符处重置计数(意即:字符是否在一行中出现了超过 X 次?)或在空格处重置计数(意即:字符是否在单词中出现了超过 X 次?)。

这是唯一与 “现实性” 相关的部分。过多的现实性将使这个示例在教育上不再有用。

支持枚举

Rust 支持使用枚举(enum)。你可以使用枚举做很多有趣的事情。

目前,只使用了一个简单的三选一的枚举,并没有其他的变形。这个枚举编码了哪种字符重置计数。

#[derive(Copy)]
enum Reset {
    NewlinesReset,
    SpacesReset,
    NoReset,
}

支持结构

接下来的 Rust 组件更大一些:这是一个结构(struct)。Rust 的结构与 Python 的 dataclass 有些相似。同样,你可以用结构做更复杂的事情。

#[pyclass]
struct Counter {
    what: char,
    min_number: u64,
    reset: Reset, 
}

实现块

你可以在 Rust 中使用一个单独的块,称为实现(impl)块,为结构添加一个方法。但具体细节超出了本文的范围。

在这个示例中,该方法调用了一个外部函数。这主要是为了分解代码。更复杂的用例将指示 Rust 编译器内联该函数,以便在不产生任何运行时成本的情况下提高可读性。

#[pymethods]
impl Counter {
    #[new]
    fn new(what: char, min_number: u64, reset: Reset) -> Self {
        Counter{what: what, min_number: min_number, reset: reset}
    }
    
    fn has_count(
        &self,
        data: &str,
    ) -> bool {
        has_count(self, data.chars())
    }
}

函数

默认情况下,Rust 变量是常量。由于当前的计数(current_count)必须更改,因此它被声明为可变变量。

fn has_count(cntr: &Counter, chars: std::str::Chars) -> bool {
    let mut current_count : u64 = 0;
    for c in chars {
        if got_count(cntr, c, &mut current_count) {
            return true;
        }
    }
    false
}

该循环遍历字符并调用 got_count 函数。再次强调,这是为了将代码分解成幻灯片展示。它展示了如何向函数发送可变引用。

尽管 current_count 是可变的,但发送和接收站点都显式标记该引用为可变。这可以清楚地表明哪些函数可能修改一个值。

计数

got_count 函数重置计数器,将其递增,然后检查它。Rust 的冒号分隔的表达式序列评估最后一个表达式的结果,即是否达到了指定的阈值。

fn got_count(cntr: &Counter, c: char, current_count: &mut u64) -> bool {
    maybe_reset(cntr, c, current_count);
    maybe_incr(cntr, c, current_count);
    *current_count >= cntr.min_number
}

重置代码

reset 的代码展示了 Rust 中另一个有用的功能:模式匹配。对 Rust 中匹配的完整描述需要一个学期级别的课程,不适合在一个无关的演讲中讲解。这个示例匹配了该元组的两个选项之一。

fn maybe_reset(cntr: &Counter, c: char, current_count: &mut u64) -> () {
    match (c, cntr.reset) {
        ('\n', Reset::NewlinesReset) | (' ', Reset::SpacesReset)=> {
            *current_count = 0;
        }
        _ => {}
    };
}

增量支持

增量将字符与所需字符进行比较,并在匹配时增加计数。

fn maybe_incr(cntr: &Counter, c: char, current_count: &mut u64) -> (){
    if c == cntr.what {
        *current_count += 1;
    };
}

请注意,我在本文中优化了代码以适合幻灯片。这不一定是 Rust 代码的最佳实践示例,也不是如何设计良好的 API 的示例。

为 Python 封装 Rust 代码

为了将 Rust 代码封装到 Python 中,你可以使用 PyO3。PyO3 Rust “crate”(即库)允许内联提示将 Rust 代码包装为 Python,使得修改两者更容易。

包含 PyO3 crate 原语

首先,你必须包含 PyO3 crate 原语。

use pyo3::prelude::*;

封装枚举

枚举需要被封装。derive 从句对于将枚举封装为 PyO3 是必需的,因为它们允许类被复制和克隆,使它们更容易在 Python 中使用。

#[pyclass]
#[derive(Clone)]
#[derive(Copy)]
enum Reset {
    /* ... */
}

封装结构

结构同样需要被封装。在 Rust 中,这些被称为 “宏”,它们会生成所需的接口位。

#[pyclass]
struct Counter {
    /* ... */
}

封装实现

封装实现(impl)更有趣。增加了另一个名为 new 的宏。此方法被标记为 #[new],让 PyO3 知道如何为内置对象公开构造函数。

#[pymethods]
impl Counter {
    #[new]
    fn new(what: char, min_number: u64,
          reset: Reset) -> Self {
        Counter{what: what,
          min_number: min_number, reset: reset}
    }
    /* ... */
}

定义模块

最后,定义一个初始化模块的函数。此函数具有特定的签名,必须与模块同名,并用 #[pymodule] 修饰。

#[pymodule]
fn counter(_py: Python, m: &PyModule
) -> PyResult<()> {
    m.add_class::<Counter>()?;
    m.add_class::<Reset>()?;
    Ok(())
}

? 显示此函数可能失败(例如,如果类没有正确配置)。 PyResult 在导入时转换为 Python 异常。

Maturin 开发

为了快速检查,用 maturin develop 构建并将库安装到当前虚拟环境中。这有助于快速迭代。

$ maturin develop

Maturin 构建

maturin build 命令构建一个 manylinux 轮子,它可以上传到 PyPI。轮子是特定于 CPU 架构的。

Python 库

从 Python 中使用库是最简单的部分。没有任何东西表明这与在 Python 中编写代码有什么区别。这其中的一个有用方面是,如果你优化了已经有单元测试的 Python 中的现有库,你可以使用 Python 单元测试来测试 Rust 库。

导入

无论你是使用 maturin develop 还是 pip install 来安装它,导入库都是使用 import 完成的。

import counter

构造函数

构造函数的定义正好使对象可以从 Python 构建。这并不总是如此。有时仅从更复杂的函数返回对象。

cntr = counter.Counter(
    'c',
    3,
    counter.Reset.NewlinesReset,
)

调用函数

最终的收益终于来了。检查这个字符串是否至少有三个 “c” 字符:

>>> cntr.has_count("hello-c-c-c-goodbye")
True

添加一个换行符会触发剩余操作,这里没有插入换行符的三个 “c” 字符:

>>> cntr.has_count("hello-c-c-\nc-goodbye")
False

使用 Rust 和 Python 很容易

我的目标是让你相信将 Rust 和 Python 结合起来很简单。我编写了一些“粘合剂”代码。Rust 和 Python 具有互补的优点和缺点。

Rust 非常适合高性能、安全的代码。Rust 具有陡峭的学习曲线,对于快速原型解决方案而言可能有些笨拙。

Python 很容易入手,并支持非常紧密的迭代循环。Python 确实有一个“速度上限”。超过一定程度后,从 Python 中获得更好的性能就更难了。

将它们结合起来完美无缝。在 Python 中进行原型设计,并将性能瓶颈移至 Rust 中。

使用 Maturin,你的开发和部署流程更容易进行。开发、构建并享受这一组合吧!


via: https://opensource.com/article/23/3/python-loves-rust

作者:Moshe Zadka 选题:lkxed 译者:ChatGPT 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

本教程让你通过编写一个 “猜数字” 游戏来探索 Basic。

用多种语言编写同一个应用是学习新的编程语言的好方法。大多数编程语言都有某些共同点,如:

  • 变量
  • 表达式
  • 语句

这些概念是大多数编程语言的基础。当你理解了它们,你就可以开始研究其他的东西了。

编程语言通常有一些相似之处。当你了解了一种编程语言,你就可以通过认识其差异来学习另一种语言的基础知识。

用标准程序进行练习是学习新语言的一个好方法。它使你能够专注于语言,而不是程序的逻辑。在这个系列文章中,我使用了一个“猜数字”的程序,在这个程序中,计算机在 1 到 100 之间挑选一个数字,并要求你猜出来。程序循环进行,直到你猜对数字为止。

这个程序锻炼了编程语言中的几个概念:

  • 变量
  • 输入
  • 输出
  • 条件判断
  • 循环

这是学习一种新的编程语言的很好的实践。本文主要介绍 Basic。

在(Bywater)Basic 中猜数字

对于 Basic 编程语言,没有真正的标准。维基百科说:“BASIC( 初学者通用符号指令代码 Beginners' All-purpose Symbolic Instruction Code )是一个通用的高级编程语言系列,旨在方便使用”。BWBasic 的实现是在 GPL 下提供的。

你可以通过编写一个“猜数字”游戏来探索 Basic。

在 Linux 上安装 Basic

在 Debian 或 Ubuntu 中,你可以用以下方法安装 Basic:

$ apt install -y bwbasic

下载 Fedora、CentOS、Mageia 和其他任何 Linux 发行版的最新版本 tarball。解压并设置可执行,然后从终端运行它:

$ tar --extract --file bwbasic*z

$ chmod +x bywater

$ ./bywater

在 Windows 上,下载 .exe 版本

Basic 代码

下面是我的实现:

10 value$ = cint(rnd * 100) + 1
20 input "enter guess"; guess$
30 guess$ = val(guess$)
40 if guess$ < value$ then print "Too low"
50 if guess$ > value$ then print "Too high"
60 if guess$ = value$ then 80
70 goto 20
80 print "That's right"

Basic 程序可以是编号的,也可以是不编号的。通常情况下,写程序时最好不编号,但用编号的行来写,可以更容易地引用各个行。

按照惯例,编码者将行写成 10 的倍数。这种方法允许在现有的行之间插入新的行,以便进行调试。下面是我对上述方法的解释:

  • 10 行:使用内置的 rnd 函数计算一个 1 到 100 之间的随机值,该函数生成一个 0 到 1 之间的数字,不包括 1。
  • 20 行:询问一个猜测,并将该值放入 guess$ 标量变量。30 行将该值转换为一个数字。
  • 40 行和 50 行:根据比较结果,给猜测者以反馈。
  • 70 行:回到循环的起点。
  • 60 行:通过将控制权转移到 80 行来打破循环。80 行是最后一行,所以程序在这之后退出。

输出示例

下面是将该程序放入 program.bas 后的一个例子:

$ bwbasic program.bas
Bywater BASIC Interpreter/Shell, version 2.20 patch level 2
Copyright (c) 1993, Ted A. Campbell
Copyright (c) 1995-1997, Jon B. Volkoff

enter guess? 50
Too low
enter guess? 75
Too low
enter guess? 88
Too high
enter guess? 80
Too low
enter guess? 84
Too low
enter guess? 86
Too high
enter guess? 85
That's right

开始学习

这个“猜数字”游戏是学习新的编程语言的一个很好的入门程序,因为它以一种相当直接的方式锻炼了几个常见的编程概念。通过在不同的编程语言中实现这个简单的游戏,你可以展示这些语言的一些核心概念,并比较它们的细节。

你有喜欢的编程语言吗?你会如何用它来写“猜数字”的游戏?请关注本系列文章,看看你可能感兴趣的其他编程语言的例子吧!


via: https://opensource.com/article/23/2/learn-basic-coding-game

作者:Moshe Zadka 选题:lkxed 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

这个 "猜数字 "游戏是学习新编程语言的一个很好的入门程序,因为它以一种相当直接的方式锻炼了几个常见的编程概念。

当你想 学习一种新的编程语言 时,把注意力放在编程语言的共同点上是很好的:

  • 变量
  • 表达式
  • 语句

这些概念是大多数编程语言的基础。一旦你理解了它们,你就可以开始琢磨其他的东西了。因为编程语言通常有相似之处,一旦你知道一种语言,你就可以通过了解其差异来学习另一种语言的基础知识。

学习新语言的一个好方法是用一个标准程序进行练习。这使你能够专注于语言,而不是程序的逻辑。在这个系列文章中,我使用了一个“猜数字”的程序,在这个程序中,计算机在 1 到 100 之间挑选一个数字,并要求你猜出来。程序循环进行,直到你猜对数字为止。

这个程序锻炼了编程语言中的几个概念:

  • 变量
  • 输入
  • 输出
  • 条件判断
  • 循环

这是一个学习新的编程语言的很好的实践实验。

安装 Ada

Ada 编程语言 是一种独特的、高度结构化的语言,有专门一群开发者使用它。Ada 的工具链是 GNU Ada 开发环境,多被称为 GNAT。

你可以使用你的发行版的包管理器在 Linux 上安装 GNAT。在 Fedora、CentOS 或类似系统上:

$ sudo dnf install gcc-gnat

在 Debian、Linux Mint 及衍生版上:

$ sudo apt install gnat

在 macOS 和 Windows 上,你可以从 Adacore 网站 下载一个安装程序(从下拉菜单中选择你的平台)。

在 Ada 中猜数字

创建一个名为 game.adb 的文件。

这个程序使用的两个内置 Ada 库:Text_IONumerics.Discrete_Random

with Ada.Text_IO;
use Ada.Text_IO;
with Ada.Numerics.Discrete_Random;

过程头

过程 procedure 的名称必须与文件的名称一致。第一部分是定义变量。

注意,discrete_random 是专门针对特定范围的。在这里,允许数字范围:

procedure Game is
   type randRange is range 1..100;
   package Rand_Int is new ada.numerics.discrete_random(randRange);
   use Rand_Int;
   gen : Generator;
   num : randRange;
   incorrect: Boolean := True;
   guess: randRange;

过程逻辑

该逻辑从 reset(gen) 开始。这将初始化随机数发生器,确保每次运行程序时,用 random(gen) 初始化的数字将是不同的。

下一步是运行循环:

  • 输出猜测的指令
  • 读取该行
  • 将其转换为 randRange
  • 将其与数字进行核对

如果数字匹配,incorrect 被设置为 False,导致循环的下一次迭代退出。

最后,程序在退出前会打印出对猜测正确性的确认:

begin
   reset(gen);
   num := random(gen);
   while incorrect loop
       Put_Line ("Guess a number between 1 and 100");
       declare
          guess_str : String := Get_Line (Current_Input);
       begin
          guess := randRange'Value (guess_str);
       end;
       if guess < num then
           Put_line("Too low");
       elsif guess > num then
           Put_line("Too high");
       else
           incorrect := False;
       end if;
   end loop;
   Put_line("That's right");
end Game;

编译程序

编译 Ada 程序的最简单方法是使用 gnatmake

$ gnatmake game.adb
aarch64-linux-gnu-gcc-10 -c game.adb
aarch64-linux-gnu-gnatbind-10 -x game.ali
aarch64-linux-gnu-gnatlink-10 game.ali

这将生成一个名为 game 的二进制文件。

运行程序

程序的每次运行都会有一些不同。这是一个例子:

$ ./game 
Guess a number between 1 and 100
50
Too low
Guess a number between 1 and 100
75
Too low
Guess a number between 1 and 100
82
Too low
Guess a number between 1 and 100
90
Too high
Guess a number between 1 and 100
87
Too low
Guess a number between 1 and 100
88
That's right

学习 Ada

这个“猜数字”游戏是学习新的编程语言的一个很好的入门程序,因为它以一种相当直接的方式锻炼了几个常见的编程概念。通过在不同的编程语言中实现这个简单的游戏,你可以展示这些语言的一些核心概念,并比较它们的细节。

你有喜欢的编程语言吗?你会如何用它来写“猜数字”的游戏?请关注本系列文章,看看你可能感兴趣的其他编程语言的例子吧!


via: https://opensource.com/article/23/1/learn-ada-simple-game

作者:Moshe Zadka 选题:lkxed 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

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

 title=

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

  • 代码是否在运行?
  • 是不是在正常工作?
  • 谁在使用它,如何使用?

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

为什么可观测性很重要

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

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

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

反馈

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

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

监控

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

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

警报

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

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

高质量警报有三个特点:

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

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

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

日志

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

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

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

日志级别

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

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

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

import logging
LOGGER=logging.getLogger(__name__)

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

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

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

日志聚合

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

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

记录查询

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

指标抓取

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

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

以 Prometheus 为标准

如果你的指标聚合器是 Prometheus,那么 Prometheus 格式做为一个 端点 endpoint 是很有用的。但是,即使聚合器不是 Prometheus,也是很有用的。几乎所有的系统都包含与 Prometheus 端点兼容的 垫片 shim

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

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

使用计数器

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

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

使用仪表值

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

使用枚举值

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

分析

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

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

结构化日志

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

错误追踪

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

使用 Sentry

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

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

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

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

磨刀不误砍柴功

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

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

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


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

作者:Moshe Zadka 选题:lujun9972 译者:MCGA 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Python 的 httpx 包是一个用于 HTTP 交互的一个优秀且灵活的模块。

 title=

Python 的 httpx 包是一个复杂的 Web 客户端。当你安装它后,你就可以用它来从网站上获取数据。像往常一样,安装它的最简单方法是使用 pip 工具:

$ python -m pip install httpx --user

要使用它,把它导入到 Python 脚本中,然后使用 .get 函数从一个 web 地址获取数据:

import httpx
result = httpx.get("https://httpbin.org/get?hello=world")
result.json()["args"]

下面是这个简单脚本的输出:

    {'hello': 'world'}

HTTP 响应

默认情况下,httpx 不会在非 200 状态下引发错误。

试试这个代码:

result = httpx.get("https://httpbin.org/status/404")
result

结果是:

    <Response [404 NOT FOUND]>

可以明确地返回一个响应。添加这个异常处理:

try:
    result.raise_for_status()
except Exception as exc:
    print("woops", exc)

下面是结果:

    woops Client error '404 NOT FOUND' for url 'https://httpbin.org/status/404'
    For more information check: https://httpstatuses.com/404

自定义客户端

除了最简单的脚本之外,使用一个自定义的客户端是有意义的。除了不错的性能改进,比如连接池,这也是一个配置客户端的好地方。

例如, 你可以设置一个自定义的基本 URL:

client = httpx.Client(base_url="https://httpbin.org")
result = client.get("/get?source=custom-client")
result.json()["args"]

输出示例:

    {'source': 'custom-client'}

这对用客户端与一个特定的服务器对话的典型场景很有用。例如,使用 base_urlauth,你可以为认证的客户端建立一个漂亮的抽象:

client = httpx.Client(
    base_url="https://httpbin.org",
    auth=("good_person", "secret_password"),
)
result = client.get("/basic-auth/good_person/secret_password")
result.json()

输出:

    {'authenticated': True, 'user': 'good_person'}

你可以用它来做一件更棒的事情,就是在顶层的 “主” 函数中构建客户端,然后把它传递给其他函数。这可以让其他函数使用客户端,并让它们与连接到本地 WSGI 应用的客户端进行单元测试。

def get_user_name(client):
    result = client.get("/basic-auth/good_person/secret_password")
    return result.json()["user"]

get_user_name(client)
    'good_person'

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'application/json')])
    return [b'{"user": "pretty_good_person"}']
fake_client = httpx.Client(app=application, base_url="https://fake-server")
get_user_name(fake_client)

输出:

    'pretty_good_person'

尝试 httpx

请访问 python-httpx.org 了解更多信息、文档和教程。我发现它是一个与 HTTP 交互的优秀而灵活的模块。试一试,看看它能为你做什么。


via: https://opensource.com/article/22/3/python-httpx

作者:Moshe Zadka 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

当你把环境保持在源码控制中,开发虚拟机和容器就成了一个解决方案,而不是一个问题。

 title=

你是否曾经开始使用一台新的电脑,不管是出于自愿还是因为旧的电脑让你的魔法烟消云散,并且对花了多长时间才把所有东西都 弄好 而感到沮丧?更糟糕的是,有没有花了一些时间重新配置你的 shell 提示符,然后意识到你更喜欢以前的样子?

对我来说,当我决定要在 容器 中进行开发时,这个问题就变得很严重了。容器是非持久的。开发工具很容易解决:一个带有工具的容器镜像就可以工作。源码很容易解决:源码控制维护它,开发是在分支上。但是,如果每次我创建一个容器,我都需要仔细地配置它,这就太痛苦了。

主目录的版本控制

将配置文件保存在版本控制中一直是一个有吸引力的选择。但是天真地这么做是令人担忧的。不可能直接对 ~ 进行版本控制。

首先,太多的程序认为把秘密放在那里是安全的。此外,它也是 ~/Downloads~/Pictures 等文件夹的位置,这些文件夹可能不应该被版本化。

小心翼翼地在主目录下保留一个 .gitignore 文件来管理 includeexclude 列表是有风险的。在某些时候,其中一个路径会出错,花费了几个小时的配置会丢失,大文件会出现在 Git 历史记录中,或者最糟糕的是,秘密和密码会被泄露。当这一策略失败时,它就成了灾难性的失败。

手动维护大量的符号链接也是行不通的。版本控制的全部原因是为了避免手动维护配置。

写一个安装脚本

这暗示了在源码控制中维护点文件的第一条线索:写一个安装脚本。

就像所有好的安装脚本一样,让它 幂等:运行两次不会两次增加配置。

像所有好的安装脚本一样,让它 只做最少的事情:使用其他的技巧来指向你的源码控制中的配置文件。

~/.config 目录

现代 Linux 程序在直接在主目录中寻找配置之前,会先在 ~/.config 中寻找。最重要的例子是 git,它在 ~/.config/git 中寻找。

这意味着安装脚本可以将 ~/.config 符号链接到主目录中源码控制的管理目录中的一个目录:

#!/bin/bash
set -e
DOTFILES="$(dirname $(realpath $0))"
[ -L ~/.config ] || ln -s $DOTFILES/config ~/.config

此脚本寻找它的位置,然后将 ~/.config 链接到它被签出的地方。这意味着几乎没有关于它需要位于主目录中的位置的假设。

获取文件

大多数 shells 仍然直接在主目录下寻找文件。为了解决这个问题,你要增加一层指示。从 $DOTFILES 中获取文件意味着在修改 shell 配置时不需要重新运行安装程序。

$!/bin/bash
set -e
DOTFILES="$(dirname $(realpath $0))"
grep -q 'SETTING UP BASH' ~/.bashrc || \
  echo "source $DOTFILES/starship.bash # SETTING UP BASH" >> ~/.bashrc

再次注意,这个脚本很仔细地做了幂等:如果这一行已经在那里了,它就不会再添加。它还考虑到了你在 .bashrc 上已经做的任何编辑,虽然这不是一个好主意,但也没有必要惩罚它。

反复测试

当你把环境保持在源码控制中时,开发虚拟机和容器就成了一个解决方案,而不是一个问题。试着做一个实验。建立一个新的开发环境,克隆你的点文件,安装,并看看有什么问题。

不要只做一次。至少每周做一次。这将使你更快地完成工作,同时也会告诉你什么是不可行的。暴露问题,修复它们,然后重复。


via: https://opensource.com/article/22/2/dotfiles-source-control

作者:Moshe Zadka 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出