发布于 2026-01-06 1 阅读
0

把你的日志发给洛基

把你的日志发给洛基

我目前的一个演讲主题是可观测性,特别是分布式追踪,并以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
}
Enter fullscreen mode Exit fullscreen mode
  1. 这就是我们过去进行字符串插值的方法。
  2. 创建请求
  3. 发过去

从 Grafana 的结果来看,原型是有效的:

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>
Enter fullscreen mode Exit fullscreen mode
  1. SLF4J是接口
  2. Logback 是实现方式。
  3. 专用于 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>
Enter fullscreen mode Exit fullscreen mode
  1. 洛基附加器
  2. 洛基网址
  3. 想要多少标签都可以
  4. 常规 Logback 模式

我们的程序变得更加简单明了:

var who = //...
var logger = LoggerFactory.getLogger(Main.class.toString());
logger.info("Hello from {}!", who);
Enter fullscreen mode Exit fullscreen mode

Grafana 显示以下内容:

Grafana 显示了使用 Logabck appender 的结果

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
Enter fullscreen mode Exit fullscreen mode

此时,我们可以在容器应用程序中使用它:

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
Enter fullscreen mode Exit fullscreen mode
  1. Loki 日志记录器
  2. 要推送到的 URL
  3. 附加标签

结果如下。请注意默认标签。

Grafana 显示了使用 Docker 日志记录的结​​果

结论

从宏观角度来看,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