我在使用 kafka 向 log4j2 和 logback 发送日志时观察到一个问题。
这是我非常简单的 Springboot 应用程序的代码:
@SpringBootApplication
public class QuestionApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(QuestionApplication.class, args);
HelloWorld helloWorld = new HelloWorld();
HelloWorld.createLogs();
}
}
public class HelloWorld {
private static final Logger logger = LoggerFactory.getLogger(HelloWorld.class);
public static void createLogs() throws JsonProcessingException {
int k = 0;
boolean b = true;
while (b) {
try{
if(Math.random() > 0.5){
logger.info("ISSUE HERE -> custom logs and send log messages to Kafka topic at " +
DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
int i = 2 / 0;
b = k < Integer.MAX_VALUE;
Thread.sleep(1000);
} else{
}
} catch (Exception re){
throw new RuntimeException("ISSUE HERE (custom log) error in try " + k, re);
} finally {
try {
int j = 2 / 0;
} catch (Exception e) {
logger.error("ISSUE HERE (custom log) error in finally " + k, e);
}
}
k++;
}
}
}
仅使用 log4j2 + kafka 附加程序的 ONE 项目 使用上面的代码,在第一个项目中,我需要将日志发送到 Kafka 主题。 为此,我使用以下依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-log4j-appender</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-layout-template-json</artifactId>
</dependency>
还有这个 log4j2.xml 文件(不是 logback-spring.xml)
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info" name="spring-boot-kafka-log">
<Appenders>
<Kafka name="Kafkalol" topic="question">
<PatternLayout>
<Pattern>
{
"some field": "1.0",
"parameters": {
"class": "%logger",
"message": "%message"
}
}
</Pattern>
</PatternLayout>
<Property name="bootstrap.servers">localhost:9093</Property>
</Kafka>
<Async name="Async">
<AppenderRef ref="Kafkalol"/>
</Async>
<Console name="stdout" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5p [%-7t] %F:%L - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Kafkalol"/>
<AppenderRef ref="stdout"/>
</Root>
<Logger name="org.apache.kafka" level="WARN" /><!-- avoid recursive logging -->
</Loggers>
</Configuration>
这有效,我可以看到 kafka 主题中的所有日志。 我所说的所有日志是指我的自定义日志(来自 logger.info())和 springboot 日志
仅使用 logback + loki Appender 的项目二 使用上面相同的 java 代码,在第二个项目中,我需要将日志发送到 loki 实例。 为此,我使用以下依赖项:
<dependency>
<groupId>com.github.loki4j</groupId>
<artifactId>loki-logback-appender</artifactId>
<version>1.6.0-m1</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
这是 logback-spring.xml (不是 log4j2.xml)
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{yyyy-MM-dd HH:mm:ss} ${PID} %-5level --- [%thread] [${name},%X{traceId:-},%X{spanId:-}] %logger{36} : %msg%n
</pattern>
</encoder>
</appender>
<appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender">
<metricsEnabled>true</metricsEnabled>
<http>
<url>https://logs-prod-006.grafana.net/loki/api/v1/push</url>
<auth>
<username>813123</username>
<password>123</password>
</auth>
</http>
<format>
<label>
<pattern>name=${name},host=${HOSTNAME},level=%level,pid=${PID},thread=%thread</pattern>
<readMarkers>true</readMarkers>
</label>
<message>
<pattern>
{
"somefield":"1.0",
"playload": {
"pid":"${PID}",
"class":"%logger",
"thread":"%thread",
"traceId":"%X{traceId}",
"spanId":"%X{spanId}",
"message":"%message"
}
}
</pattern>
</message>
<sortByTime>true</sortByTime>
</format>
<verbose>false</verbose>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="LOKI"/>
</root>
</configuration>
这有效,我可以在 grafana loki 中查看所有日志。 我所说的所有日志是指我的自定义日志(来自 logger.info())和 springboot 日志
现在,在项目三中,我需要将两者结合起来,我需要将日志发送到 kafka 和 loki。
因此,我将 log4j2.xml 和 logback-spring.xml 添加到我的资源文件夹中。 我将所有依赖项合并到一个 pom 中,然后运行该程序
我希望看到两个位置的所有日志。
然而,结果是:
所有日志(logger.info()日志+默认的springboot日志都在grafana loki中
但是kafka主题中只有默认的springboot日志,缺少自定义日志。
通过 springboot 日志,我指的是:
2024-12-13T04:35:32.364+08:00 INFO 4394 --- [ main] [ ] org.example.EventApplication : No active profile set, falling back to 1 default profile: "default"
问题,请问为什么自定义日志最终只出现在grafana日志中,而没有出现在Kafka中?
如何才能看到两个地方的所有日志?
虽然您的应用程序可以同时使用多个日志记录API(例如 SLF4J、Log4j API、Apache Commons Logging (JCL)、
java.util.logging
(JUL)),但它不能使用多个日志记录实现(例如 Logback、 Log4j 核心)。
如果您有多个日志记录实现,则每个日志记录实现仅捕获由日志记录 API 子集生成的日志事件。
有关日志记录 API 和日志记录实现的更多详细信息,请参阅 Log4j 安装指南中的概念。
由于您不能同时使用 Log4j Core 和 Logback,因此您需要选择一个具有适用于 Apache Kafka 和 Grafana Loki 的附加程序的日志记录实现。 我无法找到 Apache Kafka 的维护 Logback 附加程序,但另一方面,有 Grafana Loki 的维护 Log4j Core 附加程序。
所以你需要做的就是:
spring-boot-starter-log4j2
依赖项集并删除 spring-boot-starter-logging
)。pl.tkowalcz.tjahzi:log4j2-appender
添加到您的依赖项并配置 <Loki>
附加程序。有关更多信息,请参阅附加程序的文档。备注:
Pattern
布局尝试生成类似 JSON 的数据。您的附加程序的输出将不始终是有效的 JSON,这为日志注入攻击打开了大门。使用 JSON 模板布局(需要额外的 dep)。
我们已弃用 Log4j Core 3 中的
Kafka
附加程序,因为它几乎没有用户。
我再次对 Kafka 社区进行了民意调查,询问是否有兴趣在 this dev@kafka
线程 中使用 Kafka 附加程序,但到目前为止还没有答案。
如果您需要 Kafka 附加程序来工作,请考虑回答该线程。