使用 Grafana Alloy 处理日志
本教程假设您熟悉如何设置和连接组件。它介绍了如何使用 loki.source.api
通过 HTTP 接收日志,对其进行处理和过滤,然后将其发送到 Loki。
开始之前
完成本教程需要
- 您必须先完成日志和重命名基础教程。
通过 HTTP 接收和处理日志
`loki.source.api` 组件可以通过 HTTP 接收日志。它可以用来接收来自其他 Alloy 或收集器的日志,或者直接接收来自能通过 HTTP 发送日志的应用程序的日志,然后在中心位置对其进行处理。
推荐阅读
设置 `loki.source.api` 组件
您的管线将如下所示

首先设置 `loki.source.api` 组件
loki.source.api "listener" {
http {
listen_address = "127.0.0.1"
listen_port = 9999
}
labels = { source = "api" }
forward_to = [loki.process.process_logs.receiver]
}
这是一个简单的配置。您配置 `loki.source.api` 组件监听 `127.0.0.1:9999`,并为接收到的日志条目附加 `source="api"` 标签,然后将这些条目转发到 `loki.process.process_logs` 组件的导出接收器。
处理并写入日志
推荐阅读
配置 `loki.process` 和 `loki.write` 组件
现在您已经设置了 `loki.source.api` 组件,可以配置 `loki.process` 和 `loki.write` 组件了。
// Let's send and process more logs!
loki.source.api "listener" {
http {
listen_address = "127.0.0.1"
listen_port = 9999
}
labels = { "source" = "api" }
forward_to = [loki.process.process_logs.receiver]
}
loki.process "process_logs" {
// Stage 1
stage.json {
expressions = {
log = "",
ts = "timestamp",
}
}
// Stage 2
stage.timestamp {
source = "ts"
format = "RFC3339"
}
// Stage 3
stage.json {
source = "log"
expressions = {
is_secret = "",
level = "",
log_line = "message",
}
}
// Stage 4
stage.drop {
source = "is_secret"
value = "true"
}
// Stage 5
stage.labels {
values = {
level = "",
}
}
// Stage 6
stage.output {
source = "log_line"
}
// This stage adds static values to the labels on the log line
stage.static_labels {
values = {
source = "demo-api",
}
}
forward_to = [loki.write.local_loki.receiver]
}
loki.write "local_loki" {
endpoint {
url = "https://:3100/loki/api/v1/push"
}
}
`loki.process` 中的许多 `stage.*` 块用于读取或写入从日志中提取的值的共享映射。您可以将此提取的映射视为每个阶段都可以访问的哈希映射或表,从现在开始将其称为“提取的映射”。在后续阶段,您可以使用提取的映射来过滤日志、添加或删除标签,甚至修改日志行。
注意
`stage.*` 块按它们在组件中出现的顺序(从上到下)执行。
您可以使用示例日志行来说明这一点,然后逐阶段展示提取的映射的内容。以下是示例日志行
{
"log": {
"is_secret": "true",
"level": "info",
"message": "This is a secret message!",
},
"timestamp": "2023-11-16T06:01:50Z",
}
阶段 1
stage.json {
expressions = {
log = "",
ts = "timestamp",
}
}
此阶段将日志行解析为 JSON,从中提取 `log` 和 `timestamp` 两个值,并将它们分别放入提取的映射中,键分别为 `log` 和 `ts`。
注意
提供一个空字符串是使用与输入日志行中相同键的简写形式,因此 `log = ""` 与 `log = "log"` 相同。`expressions` 对象的键最终会成为提取的映射中的键,而值则用于在解析后的日志行中进行查找。
如果这是 Python,它大致等同于
extracted_map = {}
log_line = {"log": {"is_secret": "true", "level": "info", "message": "This is a secret message!"}, "timestamp": "2023-11-16T06:01:50Z"}
extracted_map["log"] = log_line["log"]
extracted_map["ts"] = log_line["timestamp"]
执行此阶段之前的提取的映射
{}
执行此阶段之后的提取的映射
{
"log": {
"is_secret": "true",
"level": "info",
"message": "This is a secret message!",
},
"ts": "2023-11-16T06:01:50Z",
}
阶段 2
stage.timestamp {
source = "ts"
format = "RFC3339"
}
此阶段作用于您在上一阶段提取的映射中的 `ts` 值。`ts` 的值将以 `RFC3339` 格式解析,并添加为要由 Loki 摄取的 timestamp。如果您想使用日志本身中的时间戳,而不是日志被摄取的时间,这将很有用。此阶段不修改提取的映射。
阶段 3
stage.json {
source = "log"
expressions = {
is_secret = "",
level = "",
log_line = "message",
}
}
此阶段作用于提取的映射中的 `log` 值,这是您在上一阶段提取的值。此值也是一个 JSON 对象,因此您也可以从中提取值。此阶段从 `log` 值中提取三个值:`is_secret`、`level` 和 `log_line`,并将它们放入提取的映射中,键分别为 `is_secret`、`level` 和 `log_line`。
如果这是 Python,它大致等同于
extracted_map = {
"log": {
"is_secret": "true",
"level": "info",
"message": "This is a secret message!",
},
"ts": "2023-11-16T06:01:50Z",
}
source = extracted_map["log"]
extracted_map["is_secret"] = source["is_secret"]
extracted_map["level"] = source["level"]
extracted_map["log_line"] = source["message"]
执行此阶段之前的提取的映射
{
"log": {
"is_secret": "true",
"level": "info",
"message": "This is a secret message!",
},
"ts": "2023-11-16T06:01:50Z",
}
执行此阶段之后的提取的映射
{
"log": {
"is_secret": "true",
"level": "info",
"message": "This is a secret message!",
},
"ts": "2023-11-16T06:01:50Z",
"is_secret": "true",
"level": "info",
"log_line": "This is a secret message!",
}
阶段 4
stage.drop {
source = "is_secret"
value = "true"
}
此阶段作用于提取的映射中的 `is_secret` 值,这是您在上一阶段提取的值。如果 `is_secret` 的值为 `"true"`,此阶段将丢弃日志行,并且不修改提取的映射。过滤日志还有许多其他方法,但这只是一个简单的示例。有关更多信息,请参阅 [`loki.process`][loki.process#stage.drop] stage.drop 文档。
阶段 5
stage.labels {
values = {
level = "",
}
}
此阶段使用与之前相同的简写形式为日志添加标签,因此这相当于使用 `values = { level = "level" }`。此阶段向日志添加一个键为 `level` 且值为提取的映射中 `level` 的标签(示例日志行中的 `"info"`)。此阶段不修改提取的映射。
阶段 6
stage.output {
source = "log_line"
}
此阶段使用提取的映射中的 `log_line` 值来设置实际转发到 Loki 的日志行。您不是将整个 JSON blob 发送到 Loki,而是仅发送 `original_log_line["log"]["message"]` 以及您附加的一些标签。
此阶段不修改提取的映射。
整合所有部分
现在您已经准备好所有部分,可以运行 Alloy 并向其发送一些日志了。使用上一个示例中的配置修改 `config.alloy`,然后使用以下命令启动 Alloy
<BINARY_FILE_PATH> run config.alloy
替换以下内容
- *`
`*: Alloy 可执行文件的路径。
尝试执行以下命令,它将插入当前时间戳
curl localhost:9999/loki/api/v1/raw -XPOST -H "Content-Type: application/json" -d '{"log": {"is_secret": "false", "level": "debug", "message": "This is a debug message!"}, "timestamp": "'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"}'
现在您已经发送了一些日志,是时候看看它们在 Grafana 中是什么样子了。导航到 https://:3000/explore 并将数据源切换到 `Loki`。尝试查询 `{source="demo-api"}`,看看是否能找到您发送的日志。
尝试修改 `"level"`、`"message"`、`"timestamp"` 和 `"is_secret"` 的值,看看日志如何变化。您还可以尝试向 `loki.process` 组件添加更多阶段,以从日志中提取更多值,或者添加更多标签。
练习
既然您已经在用 Docker 并且 Docker 会导出日志,您可以将这些日志发送到 Loki。有关更多信息,请参阅 [`discovery.docker`](../../reference/components/discovery/discovery.docker/) 和 [`loki.source.docker`](../../reference/components/loki/loki.source.docker/) 文档。
为了确保正确的时间戳和其他标签,请务必在使用 `loki.process` 组件处理日志后再将其发送到 Loki。
尽管您之前没有使用过 `discovery.relabel` 组件,但您可以使用它将容器名称作为标签附加到日志中。有关更多信息,请参阅 [`discovery.relabel`](../../reference/components/discovery/discovery.relabel/) 文档。`discovery.relabel` 组件与 `prometheus.relabel` 组件非常相似,但它重命名的是发现的目标,而不是指标。
// Discover docker containers to collect logs from
discovery.docker "docker_containers" {
// Note that if you are using Docker Desktop Engine this may need to be changed to
// something like "unix:///${HOME}/.docker/desktop/docker.sock"
host = "unix:///var/run/docker.sock"
}
// Extract container name from __meta_docker_container_name label and add as label
discovery.relabel "docker_containers" {
targets = discovery.docker.docker_containers.targets
rule {
source_labels = ["__meta_docker_container_name"]
target_label = "container"
}
}
// Scrape logs from docker containers and send to be processed
loki.source.docker "docker_logs" {
host = "unix:///var/run/docker.sock"
targets = discovery.relabel.docker_containers.output
forward_to = [loki.process.process_logs.receiver]
}
// Process logs and send to Loki
loki.process "process_logs" {
stage.docker { }
forward_to = [loki.write.local_loki.receiver]
}
loki.write "local_loki" {
endpoint {
url = "https://:3100/loki/api/v1/push"
}
}