编辑此页面

集中式日志管理(Graylog、Logstash、Fluentd)

本指南解释了如何将日志发送到集中式日志管理系统,如 Graylog、Logstash (Elastic Stack 或 ELK - Elasticsearch、Logstash、Kibana 内部) 或 Fluentd (EFK - Elasticsearch、Fluentd、Kibana 内部)。

本文档是 Quarkus 可观察性参考指南的一部分,该指南介绍了此组件和其他可观察性相关组件。

集中日志有很多不同的方法(如果您正在使用 Kubernetes,最简单的方法是记录到控制台,并要求您的集群管理员在集群内部集成一个中央日志管理器)。在本指南中,我们将介绍如何使用受支持的 Quarkus 扩展以受支持的标准格式(如 Graylog Extended Log Format (GELF)、Elastic Common Schema (ECS) 或 OpenTelemetry Log 信号)将日志发送到外部工具。

先决条件

要完成本指南,您需要

  • 大约 15 分钟

  • 一个 IDE

  • 已安装 JDK 17+ 并正确配置了 JAVA_HOME

  • Apache Maven 3.9.9

  • Docker 和 Docker Compose 或 Podman,以及 Docker Compose

  • 如果您想使用它,可以选择 Quarkus CLI

  • 如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置

示例应用程序

以下示例都将基于相同的示例应用程序,您可以通过以下步骤创建该应用程序。

创建一个带有 REST 扩展的应用程序。您可以使用以下命令来创建它

CLI
quarkus create app org.acme:centralized-logging \
    --extension='rest' \
    --no-code
cd centralized-logging

要创建 Gradle 项目,请添加 --gradle--gradle-kotlin-dsl 选项。

有关如何安装和使用 Quarkus CLI 的更多信息,请参阅 Quarkus CLI 指南。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.24.4:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=centralized-logging \
    -Dextensions='rest' \
    -DnoCode
cd centralized-logging

要创建 Gradle 项目,请添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

对于 Windows 用户

  • 如果使用 cmd,(不要使用反斜杠 \ 并将所有内容放在同一行上)

  • 如果使用 Powershell,请将 -D 参数用双引号括起来,例如 "-DprojectArtifactId=centralized-logging"

出于演示目的,我们创建一个端点,除了记录一句话之外什么也不做。您无需在应用程序内部执行此操作。

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.jboss.logging.Logger;

@Path("/logging")
@ApplicationScoped
public class LoggingResource {
    private static final Logger LOG = Logger.getLogger(LoggingResource.class);

    @GET
    public void log() {
        LOG.info("Some useful log message");
    }

}

使用 Socket 处理程序以 ECS(Elastic Common Schema)格式将日志发送到 Elastic Stack (ELK)

您可以使用 TCP 输入以 ECS 格式将日志发送到 Logstash。为此,我们将使用 quarkus-logging-json 扩展以 JSON 格式格式化日志,并使用 socket 处理程序将日志发送到 Logstash。

$HOME/pipelines/ecs.conf 中创建以下文件

input {
  tcp {
    port => 4560
    codec => json
  }
}

filter {
  if ![span][id] and [mdc][spanId] {
    mutate { rename => { "[mdc][spanId]" => "[span][id]" } }
  }
  if ![trace][id] and [mdc][traceId] {
    mutate { rename => {"[mdc][traceId]" => "[trace][id]"} }
  }
}

output {
  stdout {}
  elasticsearch {
    hosts => ["http://elasticsearch:9200"]
  }
}

然后配置您的应用程序以 JSON 格式记录日志

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-logging-json</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-logging-json")

并指定 Logstash 端点的主机和端口。为了符合 ECS,请指定日志格式。

# to keep the logs in the usual format in the console
quarkus.log.console.json.enabled=false

quarkus.log.socket.enable=true
quarkus.log.socket.json.enabled=true
quarkus.log.socket.endpoint=localhost:4560

# to have the exception serialized into a single text element
quarkus.log.socket.json.exception-output-type=formatted

# specify the format of the produced JSON log
quarkus.log.socket.json.log-format=ECS

最后,启动组成 Elastic Stack 的组件

  • Elasticsearch

  • Logstash

  • Kibana

您可以通过以下 docker-compose.yml 文件执行此操作,该文件可以通过 docker-compose up -d 启动

# Launch Elasticsearch
version: '3.2'

services:
  elasticsearch:
    image: docker.io/elastic/elasticsearch:9.0.2
    ports:
      - "9200:9200"
      - "9300:9300"
    environment:
      ES_JAVA_OPTS: "-Xms512m -Xmx512m"
      discovery.type: "single-node"
      cluster.routing.allocation.disk.threshold_enabled: false
    networks:
      - elk

  logstash:
    image: docker.io/elastic/logstash:9.0.2
    volumes:
      - source: $HOME/pipelines
        target: /usr/share/logstash/pipeline
        type: bind
    ports:
      - "12201:12201/udp"
      - "5000:5000"
      - "9600:9600"
    networks:
      - elk
    depends_on:
      - elasticsearch

  kibana:
    image: docker.io/elastic/kibana:9.0.2
    ports:
      - "5601:5601"
    networks:
      - elk
    depends_on:
      - elasticsearch

networks:
  elk:
    driver: bridge

启动您的应用程序,您应该会看到您的日志到达 Elastic Stack 中;您可以使用 Kibana(可通过 https://:5601/ 访问)来访问它们。

使用 Syslog 处理程序将日志发送到 Fluentd

您可以使用 Syslog 输入将日志发送到 Fluentd。与 GELF 输入相反,Syslog 输入不会在一个事件中呈现多行日志。

首先,您需要创建一个带有 Elasticsearch 插件的 Fluentd 镜像。您可以使用以下 Dockerfile,该文件应在 fluentd 目录中创建。

FROM fluent/fluentd:v1.3-debian
RUN ["gem", "install", "fluent-plugin-elasticsearch", "--version", "3.7.0"]

然后,您需要在 $HOME/fluentd/fluent.conf 中创建一个 Fluentd 配置文件

<source>
  @type syslog
  port 5140
  bind 0.0.0.0
  message_format rfc5424
  tag system
</source>

<match **>
  @type elasticsearch
  host elasticsearch
  port 9200
  logstash_format true
</match>

然后,启动组成 EFK Stack 的组件

  • Elasticsearch

  • Fluentd

  • Kibana

您可以通过以下 docker-compose.yml 文件执行此操作,该文件可以通过 docker-compose up -d 启动

version: '3.2'

services:
  elasticsearch:
    image: docker.io/elastic/elasticsearch:9.0.2
    ports:
      - "9200:9200"
      - "9300:9300"
    environment:
      ES_JAVA_OPTS: "-Xms512m -Xmx512m"
      discovery.type: "single-node"
      cluster.routing.allocation.disk.threshold_enabled: false
    networks:
      - efk

  fluentd:
    build: fluentd
    ports:
      - "5140:5140/udp"
    volumes:
      - source: $HOME/fluentd
        target: /fluentd/etc
        type: bind
    networks:
      - efk
    depends_on:
      - elasticsearch

  kibana:
    image: docker.io/elastic/kibana:9.0.2
    ports:
      - "5601:5601"
    networks:
      - efk
    depends_on:
      - elasticsearch

networks:
  efk:
    driver: bridge

最后,配置您的应用程序以使用 Syslog 将日志发送到 EFK

quarkus.log.syslog.enable=true
quarkus.log.syslog.endpoint=localhost:5140
quarkus.log.syslog.protocol=udp
quarkus.log.syslog.app-name=quarkus
quarkus.log.syslog.hostname=quarkus-test

启动您的应用程序,您应该会看到您的日志到达 EFK 中:您可以使用 Kibana(可通过 https://:5601/ 访问)来访问它们。

使用 OpenTelemetry Logging 发送日志

OpenTelemetry Logging 能够将日志发送到兼容的 OpenTelemetry 收集器。其用法在指南 使用 OpenTelemetry Logging 中进行了描述。

使用 logging-gelf 扩展发送日志

此扩展已弃用,我们建议考虑本指南中描述的替代方案。

quarkus-logging-gelf 扩展将向 Quarkus 使用的基础日志记录后端 (jboss-logmanager) 添加一个 GELF 日志处理程序。默认情况下,它是禁用的,如果您启用它但仍使用另一个处理程序(默认情况下启用控制台处理程序),您的日志将被发送到两个处理程序。

您可以通过在项目基本目录中运行以下命令,将 logging-gelf 扩展添加到您的项目

CLI
quarkus extension add logging-gelf
Maven
./mvnw quarkus:add-extension -Dextensions='logging-gelf'
Gradle
./gradlew addExtension --extensions='logging-gelf'

这会将以下依赖项添加到您的构建文件中

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-logging-gelf</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-logging-gelf")

配置 GELF 日志处理程序以将日志发送到端口 12201 上的外部 UDP 端点

quarkus.log.handler.gelf.enabled=true
quarkus.log.handler.gelf.host=localhost
quarkus.log.handler.gelf.port=12201

将日志发送到 Graylog

建议改用 Syslog 处理程序。

要将日志发送到 Graylog,您首先需要启动组成 Graylog 堆栈的组件

  • MongoDB

  • Elasticsearch

  • Graylog

您可以通过以下 docker-compose.yml 文件执行此操作,该文件可以通过 docker-compose up -d 启动

version: '3.2'

services:
  elasticsearch:
    image: docker.io/elastic/elasticsearch:9.0.2
    ports:
      - "9200:9200"
    environment:
      ES_JAVA_OPTS: "-Xms512m -Xmx512m"
      discovery.type: "single-node"
      cluster.routing.allocation.disk.threshold_enabled: false
    networks:
      - graylog

  mongo:
    image: mongo:4.0
    networks:
      - graylog

  graylog:
    image: graylog/graylog:4.3.0
    ports:
      - "9000:9000"
      - "12201:12201/udp"
      - "1514:1514"
    environment:
      GRAYLOG_HTTP_EXTERNAL_URI: "http://127.0.0.1:9000/"
      # CHANGE ME (must be at least 16 characters)!
      GRAYLOG_PASSWORD_SECRET: "forpasswordencryption"
      # Password: admin
      GRAYLOG_ROOT_PASSWORD_SHA2: "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918"
    networks:
      - graylog
    depends_on:
      - elasticsearch
      - mongo

networks:
  graylog:
    driver: bridge

然后,您需要在 Graylog 中创建一个 UDP 输入。您可以从 Graylog Web 控制台(系统 → 输入 → 选择 GELF UDP)(可通过 https://:9000 访问)或通过 API 执行此操作。

此 curl 示例将创建一个 GELF UDP 类型的新输入,它使用 Graylog 的默认登录名 (admin/admin)。

curl -H "Content-Type: application/json" -H "Authorization: Basic YWRtaW46YWRtaW4=" -H "X-Requested-By: curl" -X POST -v -d \
'{"title":"udp input","configuration":{"recv_buffer_size":262144,"bind_address":"0.0.0.0","port":12201,"decompress_size_limit":8388608},"type":"org.graylog2.inputs.gelf.udp.GELFUDPInput","global":true}' \
https://:9000/api/system/inputs

启动您的应用程序,您应该会看到您的日志到达 Graylog 中。

将日志发送到 Logstash / Elastic Stack (ELK)

建议改用 OpenTelemetry Logging 或 Socket 处理程序。

Logstash 默认带有一个可以理解 GELF 格式的输入插件,我们将首先创建一个启用此插件的管道。

$HOME/pipelines/gelf.conf 中创建以下文件

input {
  gelf {
    port => 12201
  }
}
output {
  stdout {}
  elasticsearch {
    hosts => ["http://elasticsearch:9200"]
  }
}

最后,启动组成 Elastic Stack 的组件

  • Elasticsearch

  • Logstash

  • Kibana

您可以通过以下 docker-compose.yml 文件执行此操作,该文件可以通过 docker-compose up -d 启动

# Launch Elasticsearch
version: '3.2'

services:
  elasticsearch:
    image: docker.io/elastic/elasticsearch:9.0.2
    ports:
      - "9200:9200"
      - "9300:9300"
    environment:
      ES_JAVA_OPTS: "-Xms512m -Xmx512m"
      discovery.type: "single-node"
      cluster.routing.allocation.disk.threshold_enabled: false
    networks:
      - elk

  logstash:
    image: docker.io/elastic/logstash:9.0.2
    volumes:
      - source: $HOME/pipelines
        target: /usr/share/logstash/pipeline
        type: bind
    ports:
      - "12201:12201/udp"
      - "5000:5000"
      - "9600:9600"
    networks:
      - elk
    depends_on:
      - elasticsearch

  kibana:
    image: docker.io/elastic/kibana:9.0.2
    ports:
      - "5601:5601"
    networks:
      - elk
    depends_on:
      - elasticsearch

networks:
  elk:
    driver: bridge

启动您的应用程序,您应该会看到您的日志到达 Elastic Stack 中;您可以使用 Kibana(可通过 https://:5601/ 访问)来访问它们。

将日志发送到 Fluentd (EFK)

建议改用 OpenTelemetry Logging 或 Socket 处理程序。

首先,您需要创建一个带有所需插件的 Fluentd 镜像:elasticsearch 和 input-gelf。您可以使用以下 Dockerfile,该文件应在 fluentd 目录中创建。

FROM fluent/fluentd:v1.3-debian
RUN ["gem", "install", "fluent-plugin-elasticsearch", "--version", "3.7.0"]
RUN ["gem", "install", "fluent-plugin-input-gelf", "--version", "0.3.1"]

您可以构建镜像或让 docker-compose 为您构建它。

然后,您需要在 $HOME/fluentd/fluent.conf 中创建一个 fluentd 配置文件

<source>
  type gelf
  tag example.gelf
  bind 0.0.0.0
  port 12201
</source>

<match example.gelf>
  @type elasticsearch
  host elasticsearch
  port 9200
  logstash_format true
</match>

最后,启动组成 EFK Stack 的组件

  • Elasticsearch

  • Fluentd

  • Kibana

您可以通过以下 docker-compose.yml 文件执行此操作,该文件可以通过 docker-compose up -d 启动

version: '3.2'

services:
  elasticsearch:
    image: docker.io/elastic/elasticsearch:9.0.2
    ports:
      - "9200:9200"
      - "9300:9300"
    environment:
      ES_JAVA_OPTS: "-Xms512m -Xmx512m"
      discovery.type: "single-node"
      cluster.routing.allocation.disk.threshold_enabled: false
    networks:
      - efk

  fluentd:
    build: fluentd
    ports:
      - "12201:12201/udp"
    volumes:
      - source: $HOME/fluentd
        target: /fluentd/etc
        type: bind
    networks:
      - efk
    depends_on:
      - elasticsearch

  kibana:
    image: docker.io/elastic/kibana:9.0.2
    ports:
      - "5601:5601"
    networks:
      - efk
    depends_on:
      - elasticsearch

networks:
  efk:
    driver: bridge

启动您的应用程序,您应该会看到您的日志到达 EFK 中:您可以使用 Kibana(可通过 https://:5601/ 访问)来访问它们。

Elasticsearch 索引注意事项

请注意,默认情况下,Elasticsearch 会通过检测其类型自动映射未知字段(如果在索引设置中未禁用)。如果您使用日志参数(默认情况下包含)或如果您启用 MDC 包含(默认情况下禁用),这可能会变得棘手,因为第一个日志将定义索引中消息参数(或 MDC 参数)字段的类型。

想象一下以下情况

LOG.info("some {} message {} with {} param", 1, 2, 3);
LOG.info("other {} message {} with {} param", true, true, true);

启用日志消息参数后,发送到 Elasticsearch 的第一个日志消息将具有一个 MessageParam0 参数,其类型为 int;这将配置索引,使其具有 integer 类型的字段。当第二条消息到达 Elasticsearch 时,它将具有一个 MessageParam0 参数,其布尔值为 true,这将生成索引错误。

要解决此限制,您可以通过配置 quarkus.log.handler.gelf.include-log-message-parameters=false 来禁用通过 logging-gelf 发送日志消息参数,或者您可以配置您的 Elasticsearch 索引以将这些字段存储为文本或关键字,Elasticsearch 将自动进行从 int/boolean 到 String 的转换。

有关 Graylog 的以下文档(但其他中央日志记录堆栈也存在相同的问题):自定义索引映射

配置参考

配置通过常用的 application.properties 文件完成。

构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖

配置属性

类型

默认

确定是否启用 GELF 日志记录处理程序

环境变量:QUARKUS_LOG_HANDLER_GELF_ENABLED

显示更多

布尔值

false

Logstash/Graylog 主机的主机名/IP 地址。默认情况下,它使用 UDP,在主机名前面加上 tcp: 以切换到 TCP,例如:"tcp:localhost"

环境变量:QUARKUS_LOG_HANDLER_GELF_HOST

显示更多

字符串

localhost

端口

环境变量:QUARKUS_LOG_HANDLER_GELF_PORT

显示更多

整数

12201

GELF 版本:1.0 或 1.1

环境变量:QUARKUS_LOG_HANDLER_GELF_VERSION

显示更多

字符串

1.1

是否将 Stack-Trace 发布到 StackTrace 字段。

环境变量:QUARKUS_LOG_HANDLER_GELF_EXTRACT_STACK_TRACE

显示更多

布尔值

true

仅当 extractStackTracetrue 时使用。值为 0 将提取整个堆栈跟踪。任何正值都会遍历原因链:1 对应于 exception.getCause(),2 对应于 exception.getCause().getCause(),……负的可抛出引用从根原因端遍历异常链:-1 将提取根原因,-2 包装根原因的异常,……

环境变量:QUARKUS_LOG_HANDLER_GELF_STACK_TRACE_THROWABLE_REFERENCE

显示更多

整数

0

是否执行 Stack-Trace 过滤

环境变量:QUARKUS_LOG_HANDLER_GELF_FILTER_STACK_TRACE

显示更多

布尔值

false

Java 日期模式,请参阅 java.text.SimpleDateFormat

环境变量:QUARKUS_LOG_HANDLER_GELF_TIMESTAMP_PATTERN

显示更多

字符串

yyyy-MM-dd HH:mm:ss,SSS

logging-gelf 日志级别。

环境变量:QUARKUS_LOG_HANDLER_GELF_LEVEL

显示更多

级别

ALL

工具的名称。

环境变量:QUARKUS_LOG_HANDLER_GELF_FACILITY

显示更多

字符串

jboss-logmanager

发布附加字段

类型

默认

附加字段值。

环境变量:QUARKUS_LOG_HANDLER_GELF_ADDITIONAL_FIELD__FIELD_NAME__VALUE

显示更多

字符串

必需

附加字段类型规范。支持的类型:String、long、Long、double、Double 和 discover。Discover 是默认值(如果未指定),它会根据可解析性发现字段类型。

环境变量:QUARKUS_LOG_HANDLER_GELF_ADDITIONAL_FIELD__FIELD_NAME__TYPE

显示更多

字符串

discover

是否包含 MDC 中的所有字段。

环境变量:QUARKUS_LOG_HANDLER_GELF_INCLUDE_FULL_MDC

显示更多

布尔值

false

发送附加字段,其值从 MDC 中获得。字段名称以逗号分隔。示例:mdcFields=Application,Version,SomeOtherFieldName

环境变量:QUARKUS_LOG_HANDLER_GELF_MDC_FIELDS

显示更多

字符串

动态 MDC 字段允许您基于一个或多个正则表达式提取 MDC 值。多个正则表达式以逗号分隔。MDC 条目的名称用作 GELF 字段名称。

环境变量:QUARKUS_LOG_HANDLER_GELF_DYNAMIC_MDC_FIELDS

显示更多

字符串

基于模式的附加字段和 MDC 字段类型规范。键值对以逗号分隔。示例:my_field.*=String,business\..*\.field=double

环境变量:QUARKUS_LOG_HANDLER_GELF_DYNAMIC_MDC_FIELD_TYPES

显示更多

字符串

最大消息大小(以字节为单位)。如果超过消息大小,则附加程序将以多个块提交消息。

环境变量:QUARKUS_LOG_HANDLER_GELF_MAXIMUM_MESSAGE_SIZE

显示更多

整数

8192

包含来自日志事件的消息参数

环境变量:QUARKUS_LOG_HANDLER_GELF_INCLUDE_LOG_MESSAGE_PARAMETERS

显示更多

布尔值

true

包含源代码位置

环境变量:QUARKUS_LOG_HANDLER_GELF_INCLUDE_LOCATION

显示更多

布尔值

true

源主机名

环境变量:QUARKUS_LOG_HANDLER_GELF_ORIGIN_HOST

显示更多

字符串

绕过主机名解析。如果您没有设置 originHost 属性,并且禁用了解析,则值“unknown”将用作主机名

环境变量:QUARKUS_LOG_HANDLER_GELF_SKIP_HOSTNAME_RESOLUTION

显示更多

布尔值

false

此扩展使用 logstash-gelf 库,该库允许通过系统属性进行更多配置选项,您可以在此处访问其文档:https://logging.paluch.biz/

相关内容