JAVA也能用上Seq啦

前言

在.NET生态中,Serilog凭借其强大的结构化日志记录功能和与Seq的无缝集成,已经成为许多开发者的首选日志记录工具。Seq作为一个日志检索和仪表板工具,能够将日志中的插值转换为结构化数据,极大地方便了开发者快速检索日志、定位问题并进行简单的统计分析。这种便捷性让人难以割舍。

背景

最近需要搞一个JAVA项目,当开发环境转移到Java生态,尤其是采用Spring Boot框架时,许多开发者发现自己不得不面对一个新的挑战。Spring Boot的默认日志框架Logback,在处理日志结构化方面并不如Serilog那样给人以深刻印象。习惯了Seq带来的便捷,我自然希望在Java环境中也能找到类似的解决方案。

幸运的是,Seq提供了通过GELF(GrayLog Extended Log Format)接收日志的能力,这为Java生态中的日志结构化提供了可能。在Spring Boot 3.2中,通过引入logback-gelf的JAR包,开发者可以实现将日志以GELF格式通过UDP发送到Seq,尽管这种方式对结构化支持并不是非常友好。

GELF服务端配置

  • 安装Seq

    yaml 复制代码
    version: '3'
    services:
      seq-input-gelf:
    	image: datalust/seq-input-gelf:latest
    	depends_on:
    	  - seq
    	ports:
    	  - "12201:12201/udp"
    	environment:
    	  SEQ_ADDRESS: "http://seq:5341"
    	restart: unless-stopped
      seq:
    	image: datalust/seq:latest
    	ports:
    	  - "5341:80"
    	environment:
    	  ACCEPT_EULA: Y
    	restart: unless-stopped
    	volumes:
    	  - ./seq-data:/data
  • 安装Gelf Input

Spring配置

  • 安装logback-gelf包

    gradle

    gradle 复制代码
    implementation 'de.siegmar:logback-gelf:6.0.0'
  • 添加 logback-spring.xml

    xml 复制代码
    <configuration>
    	<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    		<!-- 设置队列的最大容量,默认值为 256 -->
    		<queueSize>512</queueSize>
    		<!-- 设置当队列满时是否丢弃新的日志事件,默认为 false -->
    		<discardingThreshold>0</discardingThreshold>
    		<!-- 引用其他的 appender,例如控制台 appender -->
    		<appender-ref ref="GELF" />
    	</appender>
    	<appender name="Console"
    			  class="ch.qos.logback.core.ConsoleAppender">
    		<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
    			<Pattern>
    				[%white(%d{HH:mm:ss.SSS}) %highlight(%-5level)] [%blue(%t)] %yellow(%C{1}): %msg%n%throwable
    			</Pattern>
    		</encoder>
    	</appender>
    	<appender name="GELF" class="de.siegmar.logbackgelf.GelfUdpAppender">
    		<!--GELF Server-->
    		<graylogHost>{{替换为Seq-Input-GELF的UDP的IP}}</graylogHost>
    		<graylogPort>12201</graylogPort>
    		<maxChunkSize>508</maxChunkSize>
    		<compressionMethod>GZIP</compressionMethod>
    		<messageIdSupplier class="de.siegmar.logbackgelf.MessageIdSupplier"/>
    		<encoder class="com.leesiper.logseqsample.utils.SeqEncoder">
    			<includeRawMessage>false</includeRawMessage>
    			<includeKeyValues>true</includeKeyValues>
    			<includeMarker>false</includeMarker>
    			<includeMdcData>true</includeMdcData>
    			<includeCallerData>false</includeCallerData>
    			<includeRootCauseData>false</includeRootCauseData>
    			<includeLevelName>false</includeLevelName>
    			<shortMessageLayout class="ch.qos.logback.classic.PatternLayout">
    				<pattern>%msg%n</pattern>
    			</shortMessageLayout>
    			<fullMessageLayout class="ch.qos.logback.classic.PatternLayout">
    				<pattern>%msg%n</pattern>
    			</fullMessageLayout>
    			<numbersAsString>false</numbersAsString>
    			<!--增加app_name 区分服务-->
    			<staticField>app_name:java-demo</staticField>
    			<!--<staticField>os_arch:${os.arch}</staticField>-->
    			<!--<staticField>os_name:${os.name}</staticField>-->
    			<!--<staticField>os_version:${os.version}</staticField>-->
    		</encoder>
    	</appender>
    
    	<!-- LOG everything at INFO level -->
    	<root level="info">
    		<appender-ref ref="ASYNC" />
    		<appender-ref ref="Console" />
    	</root>
    
    </configuration>

特别注意
logback-gelf提供的de.siegmar.logbackgelf.GelfEncoder并未进行格式化写入GELF,这自然不友好,所以直接深入源码找方案。经过一番努力,我发现,通过对Logback的encoder进行定制,可以创建一个专门的SeqEncoder,这样不仅能够保持日志的结构化特性,还能够继续享受Seq带来的各种便利

所以com.leesiper.logseqsample.utils.SeqEncoder 类是override的,可以在Github(SeqEncoder)中找到

旨在解决logback日志参数格式化转换为K/V形式,在Seq上方便检索。

其中参数化format约定为"[边界][参数key]={}" 的,

边界可以为以下字符

java 复制代码
private static final char[] Delimiter = {',',' ','.','。'};

例如你记录的日志如下:

java 复制代码
Logger logger = LoggerFactory.getLogger(LogseqsampleApplication.class);

logger.info("Hello {}","World");
logger.info("Hello world={}","World");
logger.info("log config={}",Map.of("k1","v1"));
logger.info("log config={}",Map.of("key",new String[]{"value1","value2"}));
logger.info("log config={}",Map.of("key",Map.of("k1","v1")));

可以看出 config或者world参数前都有空格,即边界符。

如果运气好,配置没什么问题,Seq上可以看到日志

这一发现对于习惯了.NET生态中Serilog和Seq搭配使用的开发者来说,无疑是一个好消息。它意味着,即使在转向Java生态时,也无需放弃熟悉的日志记录习惯和工具。通过适当的配置和一些定制化开发,开发者可以在Spring Boot项目中实现与.NET生态相似的日志记录体验,继续享受快速检索和日志问题定位的便利。

总之,通过探索和创新,Java生态中的开发者同样能够享受到Serilog和Seq带来的高效日志处理体验。这不仅展示了技术跨界整合的可能性,也再次证明了开发者社区在面对挑战时不断探索和创新的精神。

Git示例仓库
spring-logback-seq

相关推荐
wuminyu6 小时前
专家视角看Java字节码加载与存储指令机制
java·linux·c语言·jvm·c++
callJJ7 小时前
Spring Data Redis 两种编程模型详解:同步 vs 响应式
java·spring boot·redis·python·spring
phltxy8 小时前
Spring Cloud 分布式服务部署实战:从 0 到 1 实现微服务上线
spring·spring cloud·微服务
wbs_scy8 小时前
Linux线程同步与互斥(三):线程同步深度解析之POSIX 信号量与环形队列生产者消费者模型,从原理到源码彻底吃透
java·开发语言
jinanwuhuaguo10 小时前
(第三十三篇)五月的文明奠基:OpenClaw 2026.5.2版本的文明级解读
android·java·开发语言·人工智能·github·拓扑学·openclaw
xmjd msup10 小时前
spring security 超详细使用教程(接入springboot、前后端分离)
java·spring boot·spring
9523611 小时前
SpringBoot统一功能处理
java·spring boot·后端
Lyyaoo.11 小时前
优惠券秒杀业务分析
java·开发语言
消失的旧时光-194311 小时前
统一并发模型:线程、Reactor、协程本质是一件事(从线程到协程 · 第6篇·终章)
java·python·算法
勿忘初心122111 小时前
Java 国密 SM4 加密工具类实战(Hutool + BouncyCastle)|企业级数据加密 + 兼容 JDK8
java·数据安全·数据加密·后端开发·企业级开发·国密 sm4