我有一个弹簧启动应用程序,该应用程序用作事件记录器。每个客户端通过REST API发送不同的事件,然后将其保存在数据库中。但是除了简单的事件外,我还需要客户将执行日志发送到Spring Boot。
现在,在客户端完成执行后,上传日志很容易,那里有很多示例。我需要的是在客户端执行,逐行执行时流式传输日志,而不要等到客户端完成。
我花了很多时间搜索了一个可能的答案,我找不到任何适合我需求的东西。有什么建议如何使用Spring Boot(包括未来版本)?它可行吗?
我在这里看到了几个可能性。首先,考虑使用logback(默认的弹簧启动记录实现)套接字Appappender或客户端中的serversocketappender。请参阅:https://logback.qos.ch/manual/appenders.html。这将使您将日志消息发送到任何记录服务。但是我可能建议您不要登录您的春季启动事件应用程序,因为我怀疑会不必要地增加您的应用程序的复杂性,而且我可以看到事件应用程序中有一些错误的情况,然后导致客户端记录一堆反过来,所有这些错误都返回到事件应用程序,因此很难确定初始错误。
我会谨此建议是,您会登录到登录服务器 - logstash:https://www.elastic.co/products/logstash,或者,如果您已经有一个将事件保存到的DB,则,然后也许使用日志dbappender并将日志直接写入db。
我在这里写了一个示例,介绍了如何在Spring Boot Endpoint中流动文件更新。唯一的区别是代码使用Java WatchService API
在给定文件上触发文件更新。
但是,在您的情况下,我还将选择日志appender直接向连接的客户端发送消息(使用SSE -CALL template.broadcast。。
端点:
@GetMapping(path = "/logs", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamSseMvc() {
return sseService.newSseEmitter();
}
服务:
public class LogsSseService {
private static final Logger log = LoggerFactory.getLogger(LogsSseService.class);
private static final String TOPIC = "logs";
private final SseTemplate template;
private static final AtomicLong COUNTER = new AtomicLong(0);
public LogsSseService(SseTemplate template, MonitoringFileService monitoringFileService) {
this.template = template;
monitoringFileService.listen(file -> {
try {
Files.lines(file)
.skip(COUNTER.get())
.forEach(line ->
template.broadcast(TOPIC, SseEmitter.event()
.id(String.valueOf(COUNTER.incrementAndGet()))
.data(line)));
} catch (IOException e) {
e.printStackTrace();
}
});
}
public SseEmitter newSseEmitter() {
return template.newSseEmitter(TOPIC);
}
}
自定义appender(您必须添加到记录器 - 在此处检查):
public class StreamAppender extends UnsynchronizedAppenderBase<ILoggingEvent> implements SmartLifecycle {
public static final String TOPIC = "logs";
private final SseTemplate template;
public StreamAppender(SseTemplate template) {
this.template = template;
}
@Override
protected void append(ILoggingEvent event) {
template.broadcast(TOPIC, SseEmitter.event()
.id(event.getThreadName())
.name("log")
.data(event.getFormattedMessage()));
}
@Override
public boolean isRunning() {
return isStarted();
}
}