日志与可观测性
Feat 框架自身不带重量级日志依赖,内置了一套轻量日志门面(tech.smartboot.feat.core.common.logging),默认基于 JDK 内置的 java.util.logging 输出。理解它的工作方式,才能在生产环境里正确地调整日志级别、接入自己的日志体系。
日志门面的工作方式
Section titled “日志门面的工作方式”框架内部所有日志都通过 LoggerFactory.getLogger(...) 获取 Logger 实例,接口与 SLF4J 风格一致,支持 {} 占位符:
import tech.smartboot.feat.core.common.logging.Logger;import tech.smartboot.feat.core.common.logging.LoggerFactory;
Logger LOGGER = LoggerFactory.getLogger(MyService.class);
LOGGER.info("user {} login from {}", userId, ip);LOGGER.error("process failed", throwable);底层实现是 RunLogger,要点有四个:
- 级别映射到 JUL:
trace → FINE、debug → CONFIG、info → INFO、warn → WARNING、error → SEVERE - 默认级别 INFO:未配置时自动设为
INFO - 级别可动态调整:
RunLogger每 5 秒检查一次系统属性,改级别不需要重启进程 - 输出到控制台:默认使用 ConsoleHandler + 自定义
LogFormatter,UTF-8 编码
配置日志级别
Section titled “配置日志级别”通过系统属性 feat.log.level 控制,启动时传入:
java -Dfeat.log.level=INFO -jar app.jar级别判断对性能友好:isInfoEnabled() 不通过时,占位符格式化不会发生。所以业务代码里可以放心打 debug 日志。
按模块单独设置级别
Section titled “按模块单独设置级别”排障时通常只想打开某个类的日志。feat.log.level.<loggerName> 可以覆盖全局级别,loggerName 就是类全名:
java -Dfeat.log.level=INFO \ -Dfeat.log.level.tech.smartboot.feat.core.server.impl.HttpMessageProcessor=CONFIG \ -jar app.jar因为是每 5 秒轮询一次系统属性,运行期通过 JMX、Arthas 之类工具改这个属性,级别也会随之生效,无需重启。
debug(true) 不是日志
Section titled “debug(true) 不是日志”ServerOptions.debug(true) 很容易被误解成”打开 debug 日志”。它做的是另一件事:给服务器挂上 StreamMonitorPlugin,把经过连接的原始字节流(请求红色、响应蓝色)直接打印到控制台。
Feat.httpServer(options -> options.debug(true)) .httpHandler(ctx -> ctx.write("hello")) .listen(8080);| 手段 | 看到什么 | 适用场景 |
|---|---|---|
feat.log.level=CONFIG | 框架的结构化日志 | 排查路由注册、加载失败等框架行为 |
debug(true) | 原始 HTTP 报文字节流 | 排查协议问题:Header 异常、编码问题、流式边界 |
生产环境接入自有日志体系
Section titled “生产环境接入自有日志体系”内置实现输出到控制台,没有文件滚动、没有对接 ELK。生产环境的推荐做法:
- 保持 Feat 日志输出到控制台,用 Docker / systemd / 重定向统一收集 stdout——容器化部署下这是最省事的路
- 如果必须接入 Logback/Log4j2,注意
LoggerFactory目前没有提供实现替换的公开接口(内部是固定的RunLogger+ 私有缓存)。框架日志量不大,通常不必强行对接;业务代码请直接使用 SLF4J 等门面,把 Feat 内置日志当”框架行为输出”看待 - 有强需求时,可以在仓库提 issue,或自行 fork 改造——
Logger接口很小,只有级别判断加各级别输出方法
排查问题的观察点
Section titled “排查问题的观察点”出问题时按这个顺序收集信息:
- 启动日志:路由是否注册成功(
Feat Router: |-> /hello ==> ...)、启动耗时 - 错误页内容:框架错误页会带状态码和描述,
WafException触发说明请求在接入层就被拦了 - 日志级别:临时把
feat.log.level调到CONFIG观察 5 秒轮询是否生效 - 报文层:本地复现时开
debug(true)对比客户端实际发出的字节
- 用故障现象反查原因:故障排查
- 交付形态与容器化:部署 Feat Core 应用