Skip to content

日志组件

框架提供了一套统一的日志组件体系,封装在 host.springboot.framework3.core.logging 包下,帮助你:

  • ✅ 使用统一格式输出业务日志与异常信息
  • ✅ 自动格式化 ApplicationException,打印 errorCode / errorMessage / userTip
  • ✅ 为 RPC 调用追加请求参数与响应结果,便于排查问题
  • ✅ 在组件类中通过 logInstance() 一键获得带标签的日志器

适用场景:业务服务日志、RPC 调用日志、网关/中间层日志等。

核心结构

主要类与接口:

  • LoggingComponent:组件日志接口(通常由业务类实现)
  • LoggingComponentProvider:在组件中使用的日志 API(trace/debug/info/warn/error)
  • LoggingExecutor:获取日志实例的工厂
  • LoggingProvider:通用日志提供器,面向底层封装场景
  • BaseLogging:统一实现日志格式组装
  • LoggingImpl:默认日志实现
  • LoggingRpcExecutor:RPC 日志扩展实现

日志格式

BaseLogging 中会将日志格式化为:

  • 普通日志:
    • [logTag] logDetailTag [k1: v1, k2: v2, ...]
  • throwableApplicationException 时:
    • [logTag] logDetailTag [k1: v1, ..., errorCode: xxx, errorMessage: xxx, userTip: xxx]

这样在一个日志行里就能同时看到:业务标签、详细标签、关键字段以及统一异常信息。

在组件中使用日志(推荐方式)

第一步:实现 LoggingComponent

java
import host.springboot.framework3.core.logging.LoggingComponent;
import host.springboot.framework3.core.logging.LoggingComponentProvider;

@Service
public class OrderService implements LoggingComponent {

    @Override
    public String logTag() {
        // 建议使用模块级别 Tag,便于过滤
        return "ORDER";
    }

    // 获取日志实例(懒加载)
    private LoggingComponentProvider logger() {
        return this.logInstance();
    }

    public void createOrder(CreateOrderRequest request) {
        logger().info("接收创建订单请求", Pair.of("userId", request.getUserId()));
        // ... 业务逻辑
        logger().debug("订单创建成功", Pair.of("orderId", orderId));
    }
}

说明:

  • logTag():组件级别标签,例如 ORDERUSERPAYMENT 等,用于快速搜索日志。
  • logInstance():由 LoggingComponent 默认实现,内部通过 LoggingExecutor.instance(this) 创建 LoggingComponentProvider
  • info/debug/warn/error/trace:均有多种重载,可带 Throwable 及多对 key/value 附加信息。

常用调用方式示例

java
// 不带附加信息
logger().info("开始处理用户下单请求");

// 带单个附加字段
logger().debug("加载用户信息", Pair.of("userId", userId));

// 带异常(例如调用下游失败)
try {
    paymentClient.pay(order);
} catch (Exception e) {
    logger().error("调用支付服务失败", e, Pair.of("orderId", orderId));
}

在非组件类中使用日志

如果你在静态工具类或非 Spring 组件中,需要手动创建日志实例,可以使用 LoggingExecutor.instance()

java
import host.springboot.framework3.core.logging.LoggingExecutor;
import host.springboot.framework3.core.logging.LoggingProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ImportTaskRunner {

    private static final Logger LOG = LoggerFactory.getLogger(ImportTaskRunner.class);
    private static final LoggingProvider LOGGING = LoggingExecutor.instance();

    public void run() {
        LOGGING.info(LOG, "IMPORT", "开始导入任务");
        try {
            // do import
            LOGGING.info(LOG, "IMPORT", "导入成功", Pair.of("count", 100));
        } catch (Exception e) {
            LOGGING.error(LOG, "IMPORT", "导入失败", e, Pair.of("step", "parse"));
        }
    }
}

要点:

  • 手工维护一个 Logger(SLF4J 原生),再配合 LoggingProvider 输出统一格式日志。
  • 适合框架内部、工具类、非 Spring Bean 场景。

RPC 调用日志

对于 RPC 场景,建议使用 LoggingExecutor.rpc(param, response),自动将请求参数与响应结果拼入日志中:

java
import host.springboot.framework3.core.logging.LoggingExecutor;
import host.springboot.framework3.core.logging.LoggingRpcExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UserRpcClient {

    private static final Logger LOG = LoggerFactory.getLogger(UserRpcClient.class);

    public UserDTO getUser(Long userId) {
        UserQueryRequest request = new UserQueryRequest(userId);
        UserDTO response = null;
        LoggingRpcExecutor rpcLogger = LoggingExecutor.rpc(request, null);
        try {
            // 调用远程服务
            response = remoteClient.getUser(request);
            rpcLogger = LoggingExecutor.rpc(request, response);
            rpcLogger.info(LOG, "USER_RPC", "调用用户服务成功");
        } catch (Exception e) {
            rpcLogger.error(LOG, "USER_RPC", "调用用户服务异常", e);
            throw e;
        }
        return response;
    }
}

LoggingRpcExecutor 会在日志中自动追加:

  • param: RPC 请求参数
  • response: RPC 响应数据

示例日志结构:

text
[USER_RPC] 调用用户服务成功 [param: {...}, response: {...}]

与异常体系的联动

当传入的 Throwable 为框架的 ApplicationException 时,BaseLogging 会自动拆解并格式化:

java
logger().error("业务处理失败", new ApplicationException(
        ErrorCodeEnum.USER_ACCOUNT_NOT_EXIST,
        "用户不存在"
));

实际输出包含:

text
[ORDER] 业务处理失败 [errorCode: A0201, errorMessage: 用户账户不存在, userTip: 用户不存在]

这样,无论是业务异常还是系统异常,日志中都能快速定位:

  • 哪个模块(logTag
  • 哪个步骤(logDetailTag
  • 哪个错误码与用户提示

最佳实践

✅ 推荐做法

  • 为每个组件实现 LoggingComponent 并返回稳定的 logTag
  • 在关键路径上使用 info + 关键字段(订单号、用户ID、TraceId 等)
  • 在异常分支使用 error,并传入 Throwable 与业务关键字段
  • RPC 调用使用 LoggingExecutor.rpc(param, response) 记录请求与响应

❌ 不推荐做法

  • 直接使用 System.out.println 打印日志
  • 混用多种格式,导致搜索困难
  • 只打印异常栈、不打印业务上下文(userId、orderId 等)

常见问题 FAQ

Q: 必须实现 LoggingComponent 吗?

A: 不是必须。业务类推荐实现 LoggingComponent 以获得更简洁的 API;工具类或框架内部可以直接使用 LoggingExecutor.instance()LoggingProvider

Q: 日志级别如何控制?

A: LoggingComponent 中的 logLevel() 会根据当前 Logger 的配置动态返回最细日志级别;你只需按语义选择 trace/debug/info/warn/error,具体是否输出由日志配置(如 logback)控制。

Q: 可否自定义日志格式?

A: 当前版本日志格式由 BaseLogging 统一实现。如需深度自定义,可在你的项目中封装自己的 LoggingProvider 实现,复用 BaseLogging 的能力或参考其实现进行扩展。

Released under the Apache-2.0 License