你好ð
在过去的几个月中,我花了很多时间在Grafana上使用Loki在MyUnisoft上创建仪表板(我为我工作的公司)。
因此,我决定从后端Node.js 开发人员的角度向您写一篇文章解释我的冒险。
ð为什么Grafana和Loki?
对于上下文,该工具是由两年前离开公司的DevOps员工实施的。所以我自己并没有自己选择该工具。我以前没有这两种工具的经验,所以我不得不进行实验和发现。
格拉法纳(Grafana)是众所周知且成熟的,所以我并不担心。另一方面,我很快喜欢Loki来管理和搜索我的日志。所以我想:“ 我必须学会从这个中制作仪表板”。
ð编写良好的日志
ð—几个日志的示例是多行的,以避免滚动和简化阅读。
编写日志时,您必须不断考虑它们将提供的信息,并确保可以在没有太多困难的情况下进行分析。取得好结果总是需要几次迭代。
根据我的经验,您可以找到两种日志:
- 调试您知道的日志对您的仪表板没有用。在错误的情况下,或者您正在寻找有关给定请求的更多上下文时,它们会派上用场。
- 所有其他可以为仪表板(以及人类读者)提供可利用数据的日志
在这里,我们打印的调试日志的示例在我们的服务启动时打印(可自定义--max-old-space-size
标志非常有用)。
const availableHeapSize = prettyBytes(
v8.getHeapStatistics().total_available_size
);
server.log.info(`Total available heap size: ${availableHeapSize}`);
另一个常见的例子是记录JSON有效载荷(当然可以利用数据,但这通常不是最终目标)。
this.entry.once("payloadReady", (payload) => {
this.log("payload before publishing:");
this.log(JSON.stringify(payload, null, 2));
});
ð±确保编辑数据(您可以在不实现的情况下泄露机密信息)。
大多数时候,您不会从这些日志中获得任何东西,并且它们不需要任何特定的努力。在本文中,我将专注于我们可以为仪表板利用的日志。
- 实例
这是我们最初为文档上传中载/插件设置的不良日志的示例
Successfully uploaded '...'
//
Failed to upload '...'
有几个问题:
- 没有范围(我们无法轻易搜索与我们的中间件相关的所有日志)。
- 可以很容易地与其他日志混合(来自其他中间Wares或服务)。
- 解析国家(失败或成功)可能是一个挑战。
- 缺乏有关请求的信息或用户。
编写它的更好方法:
[uploader|req-x5d4] document 'name.jpg' uploaded (state: ok)
您可以轻松地使用其他信息,例如扩展,大小,运行时等。
(state: ok|ext: .jpg|size: 52.5 kB|upload-time: 0.503 ms)
在记录尺寸(字节)或时间(毫秒)时,请始终使用相同的单元。在Grafana上设置正确的“单元”将为您清洁价值的工作。
ð格式澄清
必须构造一个良好的日志,以允许使用logql进行良好的搜索。这是一个很难提取信息的糟糕例子。
hello-world.jpg 52.5 kB|0.503 ms
我建议在每个值之前添加标签(人类阅读也更容易)。开始,结束和分离器字符也可以是一个很好的帮助。
doc 'hello-world.jpg' [size: 52.5 kB|exec: 0.503 ms]
在这里,所需的正格式以用loki
解析所有标签
doc '(?P<doc>\S+)' \[size: (?P<size>\S+) kB|exec: (?P<exec>\S+) ms\]
loki 2.0还允许您使用称为模式的简化语法检索标签。
pattern `doc '<doc>' [size: <size> kB|exec: <exec> ms]`
ð在框架/代码中实现
“代码”示例被简化/截断
Fastify框架默认情况下包括Pino Logger(这是一个非常出色的记录器,具有许多很酷的功能,这些功能不会损害性能)。该框架本身允许许多非常酷的东西,例如controlling the level of logs at runtime。
在我的团队中,我们选择自定义默认请求和响应日志以包括其他信息。为了实现这一目标,您需要:
- 设置fastify构造函数选项disableRequestLogging to
true
- 利用两个钩子(OnRequest和onResponse)。
server.decorateRequest("standardLog", null);
server.addHook("onRequest", async(request: FastifyRequest) => {
request.log.info(
`(${request.id}) receiving request ...`
);
request.standardLog = standardLog.bind(request);
});
server.addHook("onResponse", async(request: FastifyRequest) => {
request.log.info(
request.standardLog(
`response returned "${request.method} ${request.raw.url}"
)
);
});
StandardLog Decorator允许我们显示有关请求和使用的令牌的信息(用于身份验证的端点)。
我们必须处理几种类型的令牌(这完全取决于谁在食用API)。通过我们的合作伙伴API的经典用户或合作伙伴)。
每个人都包括:
- 客户的PostgreSQL模式
- 用户或第三方的ID
- 会计文件夹(可以为null)
function standardLog(
this: FastifyRequest,
msg: string
): string {
if (
this.server.hasRequestDecorator("tokenInfo") &&
this.tokenInfo !== null) {
const { tokenInfo } = this;
let tokenInfoLog = `${tokenInfo.type}`;
switch (tokenInfo.type) {
case "api":
tokenInfoLog += `|s:${...}|t:${...}|acf:${...}`;
break;
case "user":
tokenInfoLog += `|s:${...}|p:${...}|acf:${...}`;
break;
default:
tokenInfoLog = "";
break;
}
return `(${this.id}|${tokenInfoLog}) ${msg}`;
}
return `(${this.id}|none) ${msg}`;
}
这就是日志中的样子
(req-1rti) receiving request "POST /ged/base-docs/docs"
(req-1rti|user|s:538|p:1|acf:23) response returned
"POST /ged/base-docs/docs", statusCode: 201 (460.106ms)
这些信息对于用有关客户的信息和行动范围填充我们的仪表板至关重要。
ð记录摄入
我的团队当前使用带有YML配置的promtail检索服务日志。
server:
http_listen_port: 9080
positions:
filename: /var/lib/promtail/positions.yml
clients:
- url: https://xxx.fr/loki/api/v1/push
scrape_configs:
- job_name: svc_api_dev
static_configs:
- labels:
__path__: /home/xxx/logs/service-dev-*.log
app: api
env: dev
host: xxx.fr
job: svc_api_dev
targets:
- localhost
我不会在本章上花费太多时间,因为您可以在Internet上找到有关如何设置和配置Promtail的许多教程。
ð€
ðOCR仪表板
Myunisoft是法国会计编辑,因此 o ptalic c haracter r eccognition是我们软件的重要特征。监视对于适应客户的需求并迅速响应事件至关重要。
°°°-171117我们是由Le Monde du Ciffre授予的OCR silver medal 2023。
这是我们仅通过使用以下日志构建的仪表板的示例:
(req-2987|user|s:24|p:5710|acf:7398) OCR xxx.jpg
[type: invoice|ext: .jpg|size: 2925.73 kB]
组由标签
我们可以使用以下logQl构造上述图。注意总和 扩展。
sum(
count_over_time(
{app="ocr",env="$env"}
|= "OCR"
|= "|ext:"
| regexp `\|ext: \.(?P<extension>\S+)\|`
[$__range])
) by (extension)
REGEXP是使用Golang语法构建的(i Personaly使用regex101.com对其进行测试)。在这里提取/检测
extension
标签很有用。
我们可以使用语法$env
注入仪表板变量(在这里,我可以通过在SELECT中移动一个值来查看仪表板,分期或DEV)。
我还使用内置的$__range
变量,该变量将数据加载到Grafana当前选定的范围内。
解开
我花了很长时间才真正理解解开。互联网上几乎没有明确的文档或解释!
在这里,它将使用size
标签并提取所有值以使用min_over_time
和min
函数计算它们。这对于提取数值(计数器,执行时间等)很有用。
min(
min_over_time(
{app="ocr",env="$env"}
|= "OCR"
| regexp `\|size: (?P<size>[.0-9]+) kB\]`
| unwrap size
| __error__ = ""
[$__range])
) by (app)
ð7
| __error__ = ""
避免撞车出意外错误的loki(如果大小未包装而出于任何原因失败,可能会发生)。
然后,在统计图的右侧,我们选择了相应的单元。
ð;提示和窍门
显示名称
很长一段时间以来,我的图表中有一些非常糟糕的原始标签(具有类似于JSON的格式)。
您可以通过编辑display name
选项来自定义它。您可以使用此字段中的变量直接检索标签值。
没有数据
有时这些图将显示“无数据”,因为在选定范围内未检测到日志。在某些图形(例如测量值)中,这可能是有问题的。
,但也许您宁愿零。没问题,只需使用No value
选项。
使用REGEXP和变量过滤
在某些仪表板上,您可能希望根据几个标准动态过滤。一种方法是使用REGEXP和仪表板变量。
这是我在我的一个仪表板中所做的,以过滤一个或多个合作伙伴的结果。
count(
sum(
count_over_time(
{app="api",env="production"}
|= "] CALL"
|= "$endpoint"
|~ `\]\[$thirdparty\]`
| regexp `\((?P<schemaId>[0-9]+):(?P<folderId>[0-9]+)\)`
[$__range])
) by (schemaId, folderId)
)
这是完成作业的行:您需要使用Backticks语法来注入其中的变量。
|~ `\]\[$thirdparty\]`
ð数据是成功的关键
作为开发人员,我们没有足够的意识到监视和生成的数据如何成为有力的改进来源。在Myunisoft,我们与一百合作伙伴合作。
了解他们如何使用我们的API和他们所经历的各种异常对我们继续成倍增长至关重要。
这使我们能够不断改进,为我们的会计客户和维护集成的开发人员提供更好的体验。
我们实现和利用这些数据的能力已成为我团队专业知识的重要组成部分。看到简单的日志可以实现什么真是令人兴奋。
ð结论
我很高兴我终于设法撰写了这篇文章(我希望它带给您一些价值)。非常感谢我的团队,尤其是Câ©dric,没有他,我就不会意识到这一切。
我仍然在许多Grafana功能(例如转换或警报)中挣扎,但我将继续挖掘和改进。
ð感谢您阅读我ð