把你的日志发给洛基
我目前的一个演讲主题是可观测性,特别是分布式追踪,并以OpenTelemetry实现为例进行说明。在演示中,我将展示如何查看一个简单分布式系统的追踪信息,该系统包含:Apache APISIX API 网关、一个使用 Spring Boot 的 Kotlin 应用、一个使用 Flask 的 Python 应用以及一个使用 Axum 的 Rust 应用。
今年早些时候,我参加了 FOSDEM 的可观测性研讨会并发表了演讲。其中一个演讲演示了 Grafana 技术栈:Mimir 用于指标,Tempo 用于跟踪,Loki 用于日志。令我惊喜的是,这些工具之间可以轻松切换。因此,我希望在我的演示中实现同样的功能,但使用 OpenTelemetry 来避免与 Grafana 技术栈的耦合。
在这篇博文中,我想重点谈谈日志和洛基。
Loki基础知识和我们的第一个项目
Loki 的核心是一个日志存储引擎:
Loki 是一个受 Prometheus 启发、可水平扩展、高可用、多租户的日志聚合系统。它的设计目标是经济高效且易于操作。Loki 不索引日志内容,而是为每个日志流建立一组标签。
Loki 提供了一个RESTful API来存储和读取日志。让我们从 Java 应用程序推送一条日志。Loki 期望的有效负载结构如下:
我将使用 Java,但您也可以使用其他技术栈实现相同的结果。最简单的代码如下:
public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {
var template = "'{' \"streams\": ['{' \"stream\": '{' \"app\": \"{0}\" '}', \"values\": [[ \"{1}\", \"{2}\" ]]'}']'}'"; //1
var now = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant();
var nowInEpochNanos = NANOSECONDS.convert(now.getEpochSecond(), SECONDS) + now.getNano();
var payload = MessageFormat.format(template, "demo", String.valueOf(nowInEpochNanos), "Hello from Java App"); //1
var request = HttpRequest.newBuilder() //2
.uri(new URI("http://localhost:3100/loki/api/v1/push"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); //3
}
- 这就是我们过去进行字符串插值的方法。
- 创建请求
- 发过去
从 Grafana 的结果来看,原型是有效的:
然而,该代码存在诸多局限性:
- 标签是硬编码的。您只能发送一个标签。
- 所有内容都是硬编码的;没有任何可配置项,例如 URL。
- 这段代码对每条日志都发送一个请求;由于没有缓冲,效率极低。
- HTTP 客户端是同步的,因此在等待 Loki 时会阻塞线程。
- 完全没有错误处理机制
- Loki 同时支持 gzip 压缩和 Protobuf;我的代码都不支持这两种压缩方式。
-
最后,这与我们如何使用日志完全无关,例如:
var logger = // Obtain logger logger.info("My message with parameters {}, {}", foo, bar);
常规日志记录增强版
要使用上述语句,我们需要选择一个日志实现。因为我比较熟悉 SLF4J 和 Logback,所以我会使用它们。不用担心,同样的方法也适用于 Log4J2。
我们需要添加相关依赖项:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <!--1-->
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <!--2-->
<version>1.4.8</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.loki4j</groupId>
<artifactId>loki-logback-appender</artifactId> <!--3-->
<version>1.4.0</version>
<scope>runtime</scope>
</dependency>
- SLF4J是接口
- Logback 是实现方式。
- 专用于 SLF4J 的 Logback appender
现在,我们添加一个专门的 Loki 附加器:
<appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender"> <!--1-->
<http>
<url>http://localhost:3100/loki/api/v1/push</url> <!--2-->
</http>
<format>
<label>
<pattern>app=demo,host=${HOSTNAME},level=%level</pattern> <!--3-->
</label>
<message>
<pattern>l=%level h=${HOSTNAME} c=%logger{20} t=%thread | %msg %ex</pattern> <!--4-->
</message>
<sortByTime>true</sortByTime>
</format>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
- 洛基附加器
- 洛基网址
- 想要多少标签都可以
- 常规 Logback 模式
我们的程序变得更加简单明了:
var who = //...
var logger = LoggerFactory.getLogger(Main.class.toString());
logger.info("Hello from {}!", who);
Grafana 显示以下内容:
Docker 日志记录
我的大部分演示都是在 Docker Compose 上运行的,所以我会提到 Docker 的日志记录技巧。当容器向标准输出写入数据时,Docker 会将其保存到本地文件中。该docker logs <CONTAINER>命令可以访问该文件的内容。
除了保存到本地文件之外,还有其他选项可用,例如syslogGoogle Cloud、Splunk 等。要选择其他选项,需要设置日志驱动程序。可以在 Docker 整体级别或每个容器级别配置驱动程序。
Loki 提供自己的插件。安装方法如下:
docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions
此时,我们可以在容器应用程序中使用它:
services:
app:
build: .
logging:
driver: loki #1
options:
loki-url: http://localhost:3100/loki/api/v1/push #2
loki-external-labels: container_name={{.Name}},app=demo #3
- Loki 日志记录器
- 要推送到的 URL
- 附加标签
结果如下。请注意默认标签。
结论
从宏观角度来看,Loki 并没有什么特别之处:它只是一个简单的存储引擎,上面有一个 RESTful API。
使用该 API 有多种方法。除了最简单的方法外,我们还看到了使用 Java 日志框架 appender 和 Docker 的方法。其他方法包括通过 Kubernetes sidecar 容器抓取日志文件,例如使用 Promtail。您还可以在应用程序和 Loki 之间添加 OpenTelemetry Collector 来进行数据转换。
选择几乎是无限的。务必谨慎选择最适合您实际情况的方案。
更进一步:
原文发表于A Java Geek网站,日期为2023 年8 月 27日。
文章来源:https://dev.to/nfrankel/send-your-logs-to-loki-2ko3



