caddy日志工作原理

Caddy 具有强大且灵活的日志功能,但这些功能可能与你之前使用的功能有所不同,尤其是如果你是从传统的共享主机或其他遗留 Web 服务器迁移过来的。

概述

日志有两个主要方面:产生
消费

产生
指的是生成消息,它包含三个步骤:

  1. 收集相关信息(上下文)。

  2. 构建有用的消息表示(编码)。

  3. 将该表示发送到输出(写入)。

这些功能已经嵌入到 Caddy 的核心中,使得 Caddy 代码库的任何部分或模块(插件)都可以生成日志。

消费
则是接收和处理消息。为了发挥作用,生成的日志必须被消费。仅仅写入但从未读取的日志是没有价值的。消费日志可以简单到管理员查看控制台输出,也可以复杂到连接日志聚合工具或云服务来过滤、统计和索引日志消息。

Caddy 的角色

Caddy 是一个日志生成器
。它不会消费日志,除非是为了编码和写入日志所需的最低限度处理。这一点很重要,因为它保持了 Caddy 核心的简洁性,减少了漏洞和边缘情况,同时减轻了维护负担。最终,日志处理不在 Caddy 核心的范围内。

然而,开发一个消费日志的 Caddy 应用模块是有可能的(据我们所知,目前还没有)。

结构化日志

与大多数现代应用程序一样,Caddy 的日志是结构化的
。这意味着消息中的信息不仅仅是不透明的字符串或字节序列,而是以强类型的形式存在,并通过单独的字段名
进行键入,直到需要编码消息并将其写入时为止。

与传统的非结构化日志(例如传统的 HTTP 服务器中常用的旧式通用日志格式 CLF)进行比较:

1
2
3
4
5
6
127.0.0.1 - - [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.1" 200 2326
```

这种格式“有结构”,但不是“结构化的”:它只能用于记录 HTTP 请求。由于它是一个不透明的字节字符串,因此无法以不同的方式对其进行编码。它还缺少很多信息,甚至没有包含请求的 Host 头!这种日志格式仅在托管单个站点时有用,且只能获取关于请求的最基本信息。

现在,将 Caddy 的等效结构化日志消息(以 JSON 格式编码并格式化以方便显示)进行比较:

{
    “level”: “info”,
    “ts”: 1646861401.5241024,
    “logger”: “http.log.access”,
    “msg”: “handled request”,
    “request”: {
        “remote_ip”: “127.0.0.1”,
        “remote_port”: “41342”,
        “client_ip”: “127.0.0.1”,
        “proto”: “HTTP/2.0”,
        “method”: “GET”,
        “host”: “localhost”,
        “uri”: “/”,
        “headers”: {
            “User-Agent”: [“curl/7.82.0”],
            “Accept”: [“/”],
            “Accept-Encoding”: [“gzip, deflate, br”],
        },
        “tls”: {
            “resumed”: false,
            “version”: 772,
            “cipher_suite”: 4865,
            “proto”: “h2”,
            “server_name”: “example.com
        }
    },
    “bytes_read”: 0,
    “user_id”: “”,
    “duration”: 0.000929675,
    “size”: 10900,
    “status”: 200,
    “resp_headers”: {
        “Server”: [“Caddy”],
        “Content-Encoding”: [“gzip”],
        “Content-Type”: [“text/html; charset=utf-8”],
        “Vary”: [“Accept-Encoding”]
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  
你可以看到,结构化日志不仅更有用,而且包含了更多的信息。这种日志消息中的丰富信息不仅有用,而且几乎不会带来性能开销:Caddy 的日志是零分配的。结构化日志对数据类型或上下文没有限制:它们可以在任何代码路径中使用,并包含任何类型的信息。

由于日志是结构化的且类型明确,因此可以将其编码为任何格式。因此,如果你不想使用 JSON,日志可以编码为其他任何表示形式。Caddy 通过日志编码器模块支持其他格式,还可以添加更多。

**最重要的是**
,结构化日志和遗留格式之间的区别在于,尽管会带来性能开销,但结构化日志可以转换为遗留的通用日志格式,但反过来则不然。从 CLF 转换为结构化格式是不切实际的(或者至少是低效的),而且由于信息的缺失,这是不可能的。

本质上,高效的结构化日志通常遵循以下理念:
- 日志过多总比过少好。

- 过滤比丢弃更好。

- 延迟编码以获得更大的灵活性和互操作性。

## 日志产生

在代码中,日志产生类似于以下形式:

logger.Debug(“proxy roundtrip”, zap.String(“upstream”, di.Upstream.String()), zap.Object(“request”, caddyhttp.LoggableHTTPRequest{Request: req}), zap.Object(“headers”, caddyhttp.LoggableHTTPHeader(res.Header)), zap.Duration(“duration”, duration), zap.Int(“status”, res.StatusCode),)

  
你可以看到,这个函数调用包含了日志级别、消息和几个数据字段。所有这些内容都是强类型的,Caddy 使用了一个零分配的日志库,因此日志产生快速且高效,几乎没有开销。  
  
logger  
 变量是一个 zap.Logger  
,它可以包含任意数量的上下文,包括名称和数据字段。这使得日志记录器可以很好地从父上下文中“继承”,从而实现高级的跟踪和指标功能。  
  
从那里开始,消息会通过一个高效的处理管道,进行编码和写入。  
## 日志管道  
  
正如你在上面看到的,消息是由**日志记录器**  
产生的。然后,消息被发送到**日志**  
进行处理。  
  
Caddy 允许你配置多个日志来处理消息。一个日志包含编码器、写入器、最低级别、采样率以及要包含或排除的日志记录器列表。在 Caddy 中,始终有一个默认的日志,名为 default  
。你可以通过在配置中指定一个键为 "default"  
 的日志来对其进行自定义。  
- **编码器**  
:日志的格式。将内存中的数据表示转换为字节序列。编码器可以访问日志消息的所有字段。  
  
- **写入器**  
:日志输出。可以是任何日志写入器模块,例如写入文件或网络套接字。它只是写入字节。  
  
- **级别**  
:日志有不同的级别,从 DEBUG 到 FATAL。低于指定级别的消息将被日志忽略。  
  
- **采样**  
:非常热的路径可能会产生比能够有效处理的更多的日志;启用采样是一种减少负载的方法,同时仍然能够提供有代表性的消息样本。  
  
- **包含/排除**  
:每条消息都是由一个日志记录器产生的,它有一个名称(通常来自模块 ID)。日志可以包含或排除某些日志记录器的消息。  
  
当 Caddy 生成一条日志消息时:  
- 源日志记录器的名称会与每个日志的包含/排除列表进行检查;如果被包含(或未被排除),则该消息将被该日志接收。  
  
- 如果启用了采样,会进行快速计算以确定是否保留日志消息。  
  
- 使用日志配置的编码器对消息进行编码。  
  
- 编码后的字节将被写入到日志配置的写入器中。  
  
默认情况下,所有消息都会发送到所有配置的日志中。这符合上面描述的结构化日志的价值观。你可以通过设置它们的包含/排除列表来限制哪些消息发送到哪些日志,但这主要用于过滤来自不同模块的消息;它并不是用来像日志聚合服务那样使用的。为了保持 Caddy 日志管道的高效性,日志消息的高级处理被推迟到消费阶段。  
## 日志消费  
  
消息被发送到输出后,消费者将读取它们,对其进行解析,并相应地处理它们。  
  
这是一个与生成日志完全不同的问题领域,Caddy 的核心不会处理消费(尽管一个 Caddy 应用模块当然可以)。有许多工具可以用来处理 JSON 消息流(或其他格式)并查看、过滤、索引和查询日志。你甚至可以编写或实现自己的工具。  
  
例如,如果你运行的是需要 CLF 的遗留软件,并且需要根据特定字段(例如主机名)将日志分成不同的文件,你可以使用或编写一个简单的工具来读取 JSON  
  
,调用 sprintf()  
 创建 CLF 字符串,然后根据 request.host  
 字段中的值将其写入文件。  
  
Caddy 的日志功能还可以用于实现指标和跟踪:指标基本上是统计具有某些特征的消息,而跟踪则是根据它们之间的共同点将多条消息链接在一起。  
  
通过消费 Caddy 的日志,你可以实现无数的可能性!  
  
  

![江达小记](/images/wechatmpscan.png)